#pragma once

#include <array>
#include <memory>
#include <cstring>
#include <type_traits>
#include <typeinfo>
#include <typeindex>
#include <cassert>
#include <sstream>
#include <string>

namespace detail { namespace static_any {

enum class operation_t { query_type, query_size, copy, move, destroy };

using function_ptr_t = void(*)(operation_t operation, void* this_ptr, void* other_ptr);

template<typename _T>
static void operation(operation_t operation, void* ptr1, void* ptr2)
{
	_T* this_ptr = reinterpret_cast<_T*>(ptr1);

	switch(operation)
	{
		case operation_t::query_type:
		{
			*reinterpret_cast<const std::type_info**>(ptr1) = &typeid(_T);
			break;
		}
		case operation_t::query_size:
		{
			*reinterpret_cast<std::size_t*>(ptr1) = sizeof(_T);
			break;
		}
		case operation_t::copy:
		{
			_T* other_ptr = reinterpret_cast<_T*>(ptr2);
			assert(this_ptr);
			assert(other_ptr);
			new(this_ptr)_T(*other_ptr);
			break;
		}
		case operation_t::move:
		{
			_T* other_ptr = reinterpret_cast<_T*>(ptr2);
			assert(this_ptr);
			assert(other_ptr);
			new(this_ptr)_T(std::move(*other_ptr));
			break;
		}
		case operation_t::destroy:
		{
			assert(this_ptr);
			this_ptr->~_T();
			break;
		}
	}
}

template<typename _T>
static function_ptr_t get_function_for_type()
{
	return &static_any::operation<std::remove_cv_t<std::remove_reference_t<_T>>>;
}

}}

template <std::size_t _N>
struct static_any
{
	typedef std::size_t size_type;

	static_any() = default;

	~static_any()
	{
		destroy();
	}

	template<typename _T>
	static_any(_T&& v)
	{
		copy_or_move(std::forward<_T>(v));
	}

	template<std::size_t _M>
	static_any(const static_any<_M>& another)
	{
		copy_or_move_from_another(another);
	}

	template<std::size_t _M>
	static_any(static_any<_M>& another)
	{
		copy_or_move_from_another(std::forward<static_any<_M>>(another));
	}

	template<std::size_t _M>
	static_any(static_any<_M>&& another)
	{
		copy_or_move_from_another(std::forward<static_any<_M>>(another));
	}

	template<std::size_t _M>
	static_any& operator=(const static_any<_M>& another)
	{
		assign_from_another(another);
		return *this;
	}

	template<std::size_t _M>
	static_any& operator=(static_any<_M>& another)
	{
		assign_from_another(another);
		return *this;
	}

	template<std::size_t _M>
	static_any& operator=(static_any<_M>&& another)
	{
		assign_from_another(another);
		return *this;
	}

	template <typename _T>
	static_any& operator=(const _T& t)
	{
		assign(std::forward<_T>(t));
		return *this;
	}

	template <typename _T>
	static_any& operator=(_T&& t)
	{
		assign(std::forward<_T>(t));
		return *this;
	}

	inline void reset() { destroy(); }

	template<typename _T>
	inline const _T& get() const;

	template<typename _T>
	inline _T& get();

	template <typename _T>
	bool has() const
	{
		if (function_ == detail::static_any::get_function_for_type<_T>())
		{
			return true;
		}
		else if (function_)
		{

			return std::type_index(typeid(_T)) == std::type_index(query_type());
		}
		return false;
	}

	const std::type_info& type() const
	{
		if (empty())
			return typeid(void);
		else
			return query_type();
	}

	bool empty() const { return function_ == nullptr; }

	size_type size() const
	{
		if (empty())
			return 0;
		else
			return query_size();
	}

	static constexpr size_type capacity() { return _N; }

	template<typename _T, typename... Args>
	void emplace(Args&&... args)
	{
		destroy();
		new(buff_.data()) _T(std::forward<Args>(args)...);
		function_ = detail::static_any::get_function_for_type<_T>();
	}

private:
	using operation_t = detail::static_any::operation_t;
	using function_ptr_t = detail::static_any::function_ptr_t;

	template <typename _T>
	void copy_or_move(_T&& t)
	{
		static_assert(capacity() >= sizeof(_T), "_T is too big to be copied to static_any");
		assert(function_ == nullptr);

		using NonConstT = std::remove_cv_t<std::remove_reference_t<_T>>;
		NonConstT* non_const_t = const_cast<NonConstT*>(&t);

		try {
			call_copy_or_move<_T&&>(buff_.data(), non_const_t);
		}
		catch(...) {
			throw;
		}

		function_ = detail::static_any::get_function_for_type<_T>();
	}

	template <typename _T>
	void assign(_T&& t)
	{
		static_assert(capacity() >= sizeof(_T), "_T is too big to be copied to static_any");

		using NonConstT = std::remove_cv_t<std::remove_reference_t<_T>>;
		NonConstT* non_const_t = const_cast<NonConstT*>(&t);

		std::array<char, _N> buff;

		try {
			call_copy_or_move<_T&&>(buff.data(), non_const_t);
		}
		catch(...) {
			throw;
		}

		destroy();
		assert(function_ == nullptr);

		function_ = detail::static_any::get_function_for_type<_T>();
		buff_ = buff;
	}

	template <typename _RefT>
	std::enable_if_t<std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(void* this_void_ptr, void* other_void_ptr)
	{
		detail::static_any::get_function_for_type<_RefT>()(operation_t::move, this_void_ptr, other_void_ptr);
	}

	template <typename _RefT>
	std::enable_if_t<!std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(void* this_void_ptr, void* other_void_ptr)
	{
		detail::static_any::get_function_for_type<_RefT>()(operation_t::copy, this_void_ptr, other_void_ptr);
	}

	const std::type_info& query_type() const
	{
		assert(function_ != nullptr);
		const std::type_info* ti ;
		function_(operation_t::query_type, &ti, nullptr);
		return *ti;
	}

	size_type query_size() const
	{
		assert(function_ != nullptr);
		std::size_t size;
		function_(operation_t::query_size, &size, nullptr);
		return size;
	}

	void destroy()
	{
		if (function_)
		{
			void* not_used = nullptr;
			function_(operation_t::destroy, buff_.data(), not_used);
			function_ = nullptr;
		}
	}

	template<typename _T>
	const _T* as() const
	{
		return reinterpret_cast<const _T*>(buff_.data());
	}

	template<typename _T>
	_T* as()
	{
		return reinterpret_cast<_T*>(buff_.data());
	}

	template <typename _RefT>
	std::enable_if_t<std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(const function_ptr_t& function, void* this_void_ptr, void* other_void_ptr)
	{
		function(operation_t::move, this_void_ptr, other_void_ptr);
	}

	template <typename _RefT>
	std::enable_if_t<!std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(const function_ptr_t& function, void* this_void_ptr, void* other_void_ptr)
	{
		function(operation_t::copy, this_void_ptr, other_void_ptr);
	}

	template<std::size_t _M,
			 typename _X=std::enable_if_t<_M <= _N>>
	void copy_or_move_from_another(static_any<_M>&& another)
	{
		assert(function_ == nullptr);

		if (another.function_ == nullptr)
			return;

		void* other_data = reinterpret_cast<void*>(const_cast<char*>(another.buff_.data()));

		try {
			call_copy_or_move<static_any<_M>&&>(another.function_, buff_.data(), other_data);
		}
		catch(...) {
			throw;
		}

		function_= another.function_;
	}

	template<std::size_t _M,
			 typename _X=std::enable_if_t<_M <= _N>>
	void assign_from_another(const static_any<_M>& another)
	{
		if (another.function_ == nullptr)
			return;

		void* other_data = reinterpret_cast<void*>(const_cast<char*>(another.buff_.data()));
		std::array<char, _N> buff;

		try {
			another.function_(operation_t::copy, buff.data(), other_data);
		}
		catch(...) {
			throw;
		}

		destroy();

		function_= another.function_;
		buff_ = buff;
	}

	std::array<char, _N> buff_;
	function_ptr_t function_ = nullptr;

	template<std::size_t _S>
	friend struct static_any;

	template<typename _ValueT, std::size_t _S>
	friend _ValueT* any_cast(static_any<_S>*);

	template<typename _ValueT, std::size_t _S>
	friend _ValueT& any_cast(static_any<_S>&);
};

struct bad_any_cast : public std::bad_cast
{
	bad_any_cast(const std::type_info& from,
				 const std::type_info& to);

	virtual ~bad_any_cast();

	bad_any_cast(const bad_any_cast&) =default;

	const std::type_info& stored_type() const { return from_; }
	const std::type_info& target_type() const { return to_; }

	virtual const char* what() const noexcept override
	{
		return reason_.c_str();
	}

private:
	const std::type_info& from_;
	const std::type_info& to_;
	std::string reason_;
};

bad_any_cast::bad_any_cast(const std::type_info& from,
			 const std::type_info& to)
: from_(from),
  to_(to)
{
	std::ostringstream oss;
	oss << "failed conversion using any_cast: stored type "
		<< from.name()
		<< ", trying to cast to "
		<< to.name();
	reason_ = oss.str();
}

bad_any_cast::~bad_any_cast() {}

template <typename _ValueT,
		  std::size_t _S>
inline _ValueT* any_cast(static_any<_S>* a)
{
	if (!a->template has<_ValueT>())
		return nullptr;

	return a->template as<_ValueT>();
}

template <typename _ValueT,
		  std::size_t _S>
inline const _ValueT* any_cast(const static_any<_S>* a)
{
	return any_cast<const _ValueT>(const_cast<static_any<_S>*>(a));
}

template <typename _ValueT,
		  std::size_t _S>
inline _ValueT& any_cast(static_any<_S>& a)
{
	if (!a.template has<_ValueT>())
		throw bad_any_cast(a.type(), typeid(_ValueT));

	return *a.template as<_ValueT>();
}

template <typename _ValueT,
		  std::size_t _S>
inline const _ValueT& any_cast(const static_any<_S>& a)
{
	return any_cast<const _ValueT>(const_cast<static_any<_S>&>(a));
}

template <std::size_t _S>
template <typename _T>
const _T& static_any<_S>::get() const
{
	return any_cast<_T>(*this);
}

template <std::size_t _S>
template <typename _T>
_T& static_any<_S>::get()
{
	return any_cast<_T>(*this);
}

template <std::size_t _N>
struct static_any_t
{
	typedef std::size_t size_type;

	static constexpr size_type capacity() { return _N; }

	static_any_t() = default;
	static_any_t(const static_any_t&) = default;

	template <typename _ValueT>
	static_any_t(_ValueT&& t)
	{
		copy(std::forward<_ValueT>(t));
	}

	template <typename _ValueT>
	static_any_t& operator=(_ValueT&& t)
	{
		copy(std::forward<_ValueT>(t));
		return *this;
	}

	template <typename _ValueT>
	_ValueT& get() { return *reinterpret_cast<_ValueT*>(buff_.data()); }

	template <typename _ValueT>
	const _ValueT& get() const { return *reinterpret_cast<const _ValueT*>(buff_.data()); }

private:
	template <typename _ValueT>
	void copy(_ValueT&& t)
	{
		using NonConstT = std::remove_cv_t<std::remove_reference_t<_ValueT>>;

#if __GNUG__ && __GNUC__ < 5
		static_assert(std::has_trivial_copy_constructor<NonConstT>::value, "_ValueT is not trivially copyable");
#else
		static_assert(std::is_trivially_copyable<NonConstT>::value, "_ValueT is not trivially copyable");
#endif

		static_assert(capacity() >= sizeof(_ValueT), "_ValueT is too big to be copied to static_any");

		std::memcpy(buff_.data(), reinterpret_cast<char*>(&t), sizeof(_ValueT));
	}

	std::array<char, _N> buff_;
};
#pragma once

#include <array>
#include <memory>
#include <cstring>
#include <type_traits>
#include <typeinfo>
#include <typeindex>
#include <cassert>
#include <sstream>
#include <string>

namespace detail { namespace static_any {

// Pointer to administrative function, function that will by type-specific, and will be able to perform all the required operations
enum class operation_t { query_type, query_size, copy, move, destroy };

using function_ptr_t = void(*)(operation_t operation, void* this_ptr, void* other_ptr);

template<typename _T>
static void operation(operation_t operation, void* ptr1, void* ptr2)
{
	_T* this_ptr = reinterpret_cast<_T*>(ptr1);

	switch(operation)
	{
		case operation_t::query_type:
		{
			*reinterpret_cast<const std::type_info**>(ptr1) = &typeid(_T);
			break;
		}
		case operation_t::query_size:
		{
			*reinterpret_cast<std::size_t*>(ptr1) = sizeof(_T);
			break;
		}
		case operation_t::copy:
		{
			_T* other_ptr = reinterpret_cast<_T*>(ptr2);
			assert(this_ptr);
			assert(other_ptr);
			new(this_ptr)_T(*other_ptr);
			break;
		}
		case operation_t::move:
		{
			_T* other_ptr = reinterpret_cast<_T*>(ptr2);
			assert(this_ptr);
			assert(other_ptr);
			new(this_ptr)_T(std::move(*other_ptr));
			break;
		}
		case operation_t::destroy:
		{
			assert(this_ptr);
			this_ptr->~_T();
			break;
		}
	}
}

template<typename _T>
static function_ptr_t get_function_for_type()
{
	return &static_any::operation<std::remove_cv_t<std::remove_reference_t<_T>>>;
}

}}

template <std::size_t _N>
struct static_any
{
	typedef std::size_t size_type;

	static_any() = default;

	~static_any()
	{
		destroy();
	}

	template<typename _T>
	static_any(_T&& v)
	{
		copy_or_move(std::forward<_T>(v));
	}

	template<std::size_t _M>
	static_any(const static_any<_M>& another)
	{
		copy_or_move_from_another(another);
	}

	template<std::size_t _M>
	static_any(static_any<_M>& another)
	{
		copy_or_move_from_another(std::forward<static_any<_M>>(another));
	}

	template<std::size_t _M>
	static_any(static_any<_M>&& another)
	{
		copy_or_move_from_another(std::forward<static_any<_M>>(another));
	}

	template<std::size_t _M>
	static_any& operator=(const static_any<_M>& another)
	{
		assign_from_another(another);
		return *this;
	}

	template<std::size_t _M>
	static_any& operator=(static_any<_M>& another)
	{
		assign_from_another(another);
		return *this;
	}

	template<std::size_t _M>
	static_any& operator=(static_any<_M>&& another)
	{
		assign_from_another(another);
		return *this;
	}

	template <typename _T>
	static_any& operator=(const _T& t)
	{
		assign(std::forward<_T>(t));
		return *this;
	}

	template <typename _T>
	static_any& operator=(_T&& t)
	{
		assign(std::forward<_T>(t));
		return *this;
	}

	inline void reset() { destroy(); }

	template<typename _T>
	inline const _T& get() const;

	template<typename _T>
	inline _T& get();

	template <typename _T>
	bool has() const
	{
		if (function_ == detail::static_any::get_function_for_type<_T>())
		{
			return true;
		}
		else if (function_)
		{
			// need to try another, possibly more costly way, as we may compare types across DLL boundaries
			return std::type_index(typeid(_T)) == std::type_index(query_type());
		}
		return false;
	}

	const std::type_info& type() const
	{
		if (empty())
			return typeid(void);
		else
			return query_type();
	}

	bool empty() const { return function_ == nullptr; }

	size_type size() const
	{
		if (empty())
			return 0;
		else
			return query_size();
	}

	static constexpr size_type capacity() { return _N; }

	// Initializes with object of type T, created in-place with specified constructor params
	template<typename _T, typename... Args>
	void emplace(Args&&... args)
	{
		destroy();
		new(buff_.data()) _T(std::forward<Args>(args)...);
		function_ = detail::static_any::get_function_for_type<_T>();
	}

private:
	using operation_t = detail::static_any::operation_t;
	using function_ptr_t = detail::static_any::function_ptr_t;

	template <typename _T>
	void copy_or_move(_T&& t)
	{
		static_assert(capacity() >= sizeof(_T), "_T is too big to be copied to static_any");
		assert(function_ == nullptr);

		using NonConstT = std::remove_cv_t<std::remove_reference_t<_T>>;
		NonConstT* non_const_t = const_cast<NonConstT*>(&t);

		try {
			call_copy_or_move<_T&&>(buff_.data(), non_const_t);
		}
		catch(...) {
			throw;
		}

		function_ = detail::static_any::get_function_for_type<_T>();
	}

	template <typename _T>
	void assign(_T&& t)
	{
		static_assert(capacity() >= sizeof(_T), "_T is too big to be copied to static_any");

		using NonConstT = std::remove_cv_t<std::remove_reference_t<_T>>;
		NonConstT* non_const_t = const_cast<NonConstT*>(&t);

		std::array<char, _N> buff;

		try {
			call_copy_or_move<_T&&>(buff.data(), non_const_t);
		}
		catch(...) {
			throw;
		}

		destroy();
		assert(function_ == nullptr);

		function_ = detail::static_any::get_function_for_type<_T>();
		buff_ = buff;
	}

	template <typename _RefT>
	std::enable_if_t<std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(void* this_void_ptr, void* other_void_ptr)
	{
		detail::static_any::get_function_for_type<_RefT>()(operation_t::move, this_void_ptr, other_void_ptr);
	}

	template <typename _RefT>
	std::enable_if_t<!std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(void* this_void_ptr, void* other_void_ptr)
	{
		detail::static_any::get_function_for_type<_RefT>()(operation_t::copy, this_void_ptr, other_void_ptr);
	}

	const std::type_info& query_type() const
	{
		assert(function_ != nullptr);
		const std::type_info* ti ;
		function_(operation_t::query_type, &ti, nullptr);
		return *ti;
	}

	size_type query_size() const
	{
		assert(function_ != nullptr);
		std::size_t size;
		function_(operation_t::query_size, &size, nullptr);
		return size;
	}

	void destroy()
	{
		if (function_)
		{
			void* not_used = nullptr;
			function_(operation_t::destroy, buff_.data(), not_used);
			function_ = nullptr;
		}
	}

	template<typename _T>
	const _T* as() const
	{
		return reinterpret_cast<const _T*>(buff_.data());
	}

	template<typename _T>
	_T* as()
	{
		return reinterpret_cast<_T*>(buff_.data());
	}

	template <typename _RefT>
	std::enable_if_t<std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(const function_ptr_t& function, void* this_void_ptr, void* other_void_ptr)
	{
		function(operation_t::move, this_void_ptr, other_void_ptr);
	}

	template <typename _RefT>
	std::enable_if_t<!std::is_rvalue_reference<_RefT>::value>
	call_copy_or_move(const function_ptr_t& function, void* this_void_ptr, void* other_void_ptr)
	{
		function(operation_t::copy, this_void_ptr, other_void_ptr);
	}

	template<std::size_t _M,
			 typename _X=std::enable_if_t<_M <= _N>>
	void copy_or_move_from_another(static_any<_M>&& another)
	{
		assert(function_ == nullptr);

		if (another.function_ == nullptr)
			return;

		void* other_data = reinterpret_cast<void*>(const_cast<char*>(another.buff_.data()));

		try {
			call_copy_or_move<static_any<_M>&&>(another.function_, buff_.data(), other_data);
		}
		catch(...) {
			throw;
		}

		function_= another.function_;
	}

	template<std::size_t _M,
			 typename _X=std::enable_if_t<_M <= _N>>
	void assign_from_another(const static_any<_M>& another)
	{
		if (another.function_ == nullptr)
			return;

		void* other_data = reinterpret_cast<void*>(const_cast<char*>(another.buff_.data()));
		std::array<char, _N> buff;

		try {
			another.function_(operation_t::copy, buff.data(), other_data);
		}
		catch(...) {
			throw;
		}

		destroy();

		function_= another.function_;
		buff_ = buff;
	}

	std::array<char, _N> buff_;
	function_ptr_t function_ = nullptr;

	template<std::size_t _S>
	friend struct static_any;

	template<typename _ValueT, std::size_t _S>
	friend _ValueT* any_cast(static_any<_S>*);

	template<typename _ValueT, std::size_t _S>
	friend _ValueT& any_cast(static_any<_S>&);
};

struct bad_any_cast : public std::bad_cast
{
	bad_any_cast(const std::type_info& from,
				 const std::type_info& to);

	virtual ~bad_any_cast();

	bad_any_cast(const bad_any_cast&) =default;

	const std::type_info& stored_type() const { return from_; }
	const std::type_info& target_type() const { return to_; }

	virtual const char* what() const noexcept override
	{
		return reason_.c_str();
	}

private:
	const std::type_info& from_;
	const std::type_info& to_;
	std::string reason_;
};

bad_any_cast::bad_any_cast(const std::type_info& from,
			 const std::type_info& to)
: from_(from),
  to_(to)
{
	std::ostringstream oss;
	oss << "failed conversion using any_cast: stored type "
		<< from.name()
		<< ", trying to cast to "
		<< to.name();
	reason_ = oss.str();
}

bad_any_cast::~bad_any_cast() {}

template <typename _ValueT,
		  std::size_t _S>
inline _ValueT* any_cast(static_any<_S>* a)
{
	if (!a->template has<_ValueT>())
		return nullptr;

	return a->template as<_ValueT>();
}

template <typename _ValueT,
		  std::size_t _S>
inline const _ValueT* any_cast(const static_any<_S>* a)
{
	return any_cast<const _ValueT>(const_cast<static_any<_S>*>(a));
}

template <typename _ValueT,
		  std::size_t _S>
inline _ValueT& any_cast(static_any<_S>& a)
{
	if (!a.template has<_ValueT>())
		throw bad_any_cast(a.type(), typeid(_ValueT));

	return *a.template as<_ValueT>();
}

template <typename _ValueT,
		  std::size_t _S>
inline const _ValueT& any_cast(const static_any<_S>& a)
{
	return any_cast<const _ValueT>(const_cast<static_any<_S>&>(a));
}

template <std::size_t _S>
template <typename _T>
const _T& static_any<_S>::get() const
{
	return any_cast<_T>(*this);
}

template <std::size_t _S>
template <typename _T>
_T& static_any<_S>::get()
{
	return any_cast<_T>(*this);
}


template <std::size_t _N>
struct static_any_t
{
	typedef std::size_t size_type;

	static constexpr size_type capacity() { return _N; }

	static_any_t() = default;
	static_any_t(const static_any_t&) = default;

	template <typename _ValueT>
	static_any_t(_ValueT&& t)
	{
		copy(std::forward<_ValueT>(t));
	}

	template <typename _ValueT>
	static_any_t& operator=(_ValueT&& t)
	{
		copy(std::forward<_ValueT>(t));
		return *this;
	}

	template <typename _ValueT>
	_ValueT& get() { return *reinterpret_cast<_ValueT*>(buff_.data()); }

	template <typename _ValueT>
	const _ValueT& get() const { return *reinterpret_cast<const _ValueT*>(buff_.data()); }

private:
	template <typename _ValueT>
	void copy(_ValueT&& t)
	{
		using NonConstT = std::remove_cv_t<std::remove_reference_t<_ValueT>>;

#if __GNUG__ && __GNUC__ < 5
		static_assert(std::has_trivial_copy_constructor<NonConstT>::value, "_ValueT is not trivially copyable");
#else
		static_assert(std::is_trivially_copyable<NonConstT>::value, "_ValueT is not trivially copyable");
#endif

		static_assert(capacity() >= sizeof(_ValueT), "_ValueT is too big to be copied to static_any");

		std::memcpy(buff_.data(), reinterpret_cast<char*>(&t), sizeof(_ValueT));
	}

	std::array<char, _N> buff_;
};
