
#pragma once

#include "assert.hpp"
#include "array.hpp"
#include "def.hpp"
#include "span.hpp"

namespace ox {

class AnyPtr {
	private:
		struct WrapBase {
			virtual constexpr ~WrapBase() = default;
			virtual constexpr WrapBase *copyTo(ox::Span<char> s) noexcept = 0;
			virtual constexpr operator bool() const noexcept = 0;
		};

		template<typename T>
		struct Wrap: public WrapBase {
			T *data{};
			constexpr Wrap(T *pData) noexcept: data(pData) {
			}
			constexpr WrapBase *copyTo(ox::Span<char> s) noexcept override {
				oxAssert(s.size() >= sizeof(Wrap), "too small buffer");
				if (std::is_constant_evaluated()) {
					return new Wrap(data);
				} else {
					return new(s.data()) Wrap(data);
				}
			}
			constexpr operator bool() const noexcept override {
				return data != nullptr;
			}
		};

		WrapBase *m_wrapPtr{};
		ox::Array<char, sizeof(Wrap<void*>)> m_wrapData;

	public:
		constexpr AnyPtr() noexcept = default;

		template<typename T>
		constexpr AnyPtr(T *ptr) noexcept {
			if (std::is_constant_evaluated()) {
				m_wrapPtr = new Wrap(ptr);
			} else {
				m_wrapPtr = new(m_wrapData.data()) Wrap(ptr);
			}
		}

		constexpr AnyPtr(AnyPtr const&other) noexcept {
			if (other) {
				m_wrapPtr = other.m_wrapPtr->copyTo(m_wrapData);
			}
		}

		constexpr ~AnyPtr() noexcept {
			if (std::is_constant_evaluated()) {
				ox::safeDelete(m_wrapPtr);
			}
		}

		template<typename T>
		constexpr AnyPtr &operator=(T *ptr) noexcept {
			if (std::is_constant_evaluated()) {
				ox::safeDelete(m_wrapPtr);
				m_wrapPtr = new Wrap(ptr);
			} else {
				m_wrapPtr = new(m_wrapData.data()) Wrap(ptr);
			}
			return *this;
		}

		constexpr AnyPtr &operator=(AnyPtr const&ptr) noexcept {
			if (this != &ptr) {
				if (ptr) {
					ox::safeDelete(m_wrapPtr);
					m_wrapPtr = ptr.m_wrapPtr->copyTo(m_wrapData);
				} else {
					m_wrapPtr = nullptr;
				}
			}
			return *this;
		}

		constexpr operator bool() const noexcept {
			return m_wrapPtr && *m_wrapPtr;
		}

		template<typename T>
		[[nodiscard]]
		constexpr T *get() const noexcept {
#ifdef OX_BARE_METAL
			return static_cast<Wrap<T>*>(m_wrapPtr)->data;
#else
			return dynamic_cast<Wrap<T>*>(m_wrapPtr)->data;
#endif
		}
};

}