// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once

#include <any>

namespace base {
namespace details {

template <typename Value>
struct moveable_as_copyable_wrap {
	moveable_as_copyable_wrap(Value &&other)
	: value(std::move(other)) {
	}
	moveable_as_copyable_wrap &operator=(Value &&other) {
		value = std::move(other);
		return *this;
	}
	moveable_as_copyable_wrap(moveable_as_copyable_wrap &&other)
	: value(std::move(other.value)) {
	}
	moveable_as_copyable_wrap(
			const moveable_as_copyable_wrap &other) {
		Unexpected("Attempt to copy-construct a move-only type.");
	}
	moveable_as_copyable_wrap &operator=(
			moveable_as_copyable_wrap &&other) {
		value = std::move(other.value);
		return *this;
	}
	moveable_as_copyable_wrap &operator=(
			const moveable_as_copyable_wrap &other) {
		Unexpected("Attempt to copy-assign a move-only type.");
	}

	Value value;

};

template <
	typename Value,
	typename = std::enable_if_t<
		std::is_move_constructible_v<std::decay_t<Value>>
		&& !std::is_lvalue_reference_v<Value>>>
auto wrap_moveable_as_copyable(Value &&value) {
	return moveable_as_copyable_wrap<Value>(std::move(value));
}

} // namespace details

class unique_any;

template <typename Value>
Value *any_cast(unique_any *value) noexcept;

template <typename Value>
const Value *any_cast(const unique_any *value) noexcept;

class unique_any final {
public:
	// Construction and destruction [any.cons]
	constexpr unique_any() noexcept {
	}

	unique_any(const unique_any &other) = delete;
	unique_any &operator=(const unique_any &other) = delete;

	unique_any(unique_any &&other) noexcept
	: _impl(std::move(other._impl)) {
	}

	unique_any &operator=(unique_any &&other) noexcept {
		_impl = std::move(other._impl);
		return *this;
	}

	template <
		typename Value,
		typename = std::enable_if_t<
			!std::is_same_v<std::decay_t<Value>, unique_any>>>
	unique_any(Value &&other)
	: unique_any(
		std::forward<Value>(other),
		std::is_copy_constructible<std::decay_t<Value>>()) {
	}

	template <
		typename Value,
		typename = std::enable_if_t<
			!std::is_same_v<std::decay_t<Value>, unique_any>>>
	unique_any &operator=(Value &&other) {
		if constexpr (std::is_copy_constructible_v<std::decay_t<Value>>) {
			_impl = std::forward<Value>(other);
		} else if constexpr (std::is_move_constructible_v<std::decay_t<Value>>
			&& !std::is_lvalue_reference_v<Value>) {
			_impl = details::wrap_moveable_as_copyable(std::move(other));
		} else {
			static_assert(
				false_t(Value{}),
				"Bad value for base::unique_any.");
		}
		return *this;
	}

	template <
		typename Value,
		typename ...Args,
		typename = std::enable_if_t<
			std::is_constructible_v<std::decay_t<Value>, Args...>
			&& std::is_copy_constructible_v<decay_t<Value>>>>
	std::decay_t<Value> &emplace(Args &&...args) {
		return _impl.emplace<Value>(std::forward<Args>(args)...);
	}

	void reset() noexcept {
		_impl.reset();
	}

	void swap(unique_any &other) noexcept {
		_impl.swap(other._impl);
	}

	bool has_value() const noexcept {
		return _impl.has_value();
	}

	// Should check if it is a moveable_only wrap first.
	//const std::type_info &type() const noexcept {
	//	return _impl.type();
	//}

private:
	template <
		typename Value,
		typename = std::enable_if_t<
			!std::is_same_v<std::decay_t<Value>, unique_any>
			&& std::is_copy_constructible_v<std::decay_t<Value>>>>
	unique_any(Value &&other, std::true_type)
	: _impl(std::forward<Value>(other)) {
	}

	template <
		typename Value,
		typename = std::enable_if_t<
			!std::is_same_v<std::decay_t<Value>, unique_any>
			&& !std::is_copy_constructible_v<std::decay_t<Value>>
			&& std::is_move_constructible_v<std::decay_t<Value>>
			&& !std::is_lvalue_reference_v<Value>>>
	unique_any(Value &&other, std::false_type)
	: _impl(details::wrap_moveable_as_copyable(std::move(other))) {
	}

	template <
		typename Value,
		typename ...Args>
	friend unique_any make_any(Args &&...args);

	template <typename Value>
	friend const Value *any_cast(const unique_any *value) noexcept;

	template <typename Value>
	friend Value *any_cast(unique_any *value) noexcept;

	std::any _impl;

};

inline void swap(unique_any &a, unique_any &b) noexcept {
	a.swap(b);
}

template <
	typename Value,
	typename ...Args>
inline auto make_any(Args &&...args)
-> std::enable_if_t<
		std::is_copy_constructible_v<std::decay_t<Value>>,
		unique_any> {
	return std::make_any<Value>(std::forward<Args>(args)...);
}

template <
	typename Value,
	typename ...Args>
inline auto make_any(Args &&...args)
-> std::enable_if_t<
		!std::is_copy_constructible_v<std::decay_t<Value>>
		&& std::is_move_constructible_v<std::decay_t<Value>>,
		unique_any> {
	return Value(std::forward<Args>(args)...);
}

template <typename Value>
inline Value *any_cast(unique_any *value) noexcept {
	if constexpr (std::is_copy_constructible_v<Value>) {
		return std::any_cast<Value>(&value->_impl);
	} else if constexpr (std::is_move_constructible_v<Value>) {
		auto wrap = std::any_cast<
			details::moveable_as_copyable_wrap<Value>
		>(&value->_impl);
		return wrap ? &wrap->value : nullptr;
	} else {
		static_assert(
			false_t(Value{}),
			"Bad type for base::any_cast.");
	}
}

template <typename Value>
inline const Value *any_cast(const unique_any *value) noexcept {
	if constexpr (std::is_copy_constructible_v<Value>) {
		return std::any_cast<Value>(&value->_impl);
	} else if constexpr (std::is_move_constructible_v<Value>) {
		auto wrap = std::any_cast<
			details::moveable_as_copyable_wrap<Value>
		>(&value->_impl);
		return wrap ? &wrap->value : nullptr;
	} else {
		static_assert(
			false_t(Value{}),
			"Bad type for base::any_cast.");
	}
}

} // namespace base
