/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

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

namespace ox {

namespace detail {

template<bool unique>
class AnyPtrT {
	private:
		struct WrapBase {
			virtual constexpr ~WrapBase() = default;
			virtual constexpr WrapBase *copyTo(Span<char> s) const noexcept = 0;
			virtual constexpr operator bool() const noexcept = 0;
			virtual void free() noexcept = 0;
		};

		template<typename T>
		struct Wrap final: WrapBase {
			T *data{};
			explicit constexpr Wrap(T *pData) noexcept: data(pData) {
			}
			constexpr WrapBase *copyTo(Span<char> s) const 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;
			}
			constexpr void free() noexcept override {
				safeDelete(data);
				data = {};
			}
		};

		union {
			WrapBase *m_wrapPtr{};
			AllocAlias<Wrap<void*>> m_wrapData;
		} m_data;

	public:
		constexpr AnyPtrT() noexcept = default;

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

		constexpr AnyPtrT(AnyPtrT const &other) noexcept requires(!unique) {
			if (other) {
				setWrapPtr(other.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
			}
		}

		constexpr AnyPtrT(AnyPtrT &&other) noexcept {
			if (other) {
				setWrapPtr(other.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
				if (std::is_constant_evaluated()) {
					ox::safeDelete(m_data.m_wrapPtr);
				}
				m_data.m_wrapData = {};
			}
		}

		constexpr ~AnyPtrT() noexcept {
			if constexpr(unique) {
				free();
			}
			if (std::is_constant_evaluated()) {
				ox::safeDelete(m_data.m_wrapPtr);
			}
		}

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

		constexpr AnyPtrT &operator=(AnyPtrT const &ptr) noexcept requires(!unique) {
			if (this != &ptr) {
				if (std::is_constant_evaluated()) {
					ox::safeDelete(m_data.m_wrapPtr);
				}
				if (std::is_constant_evaluated()) {
					if (ptr) {
						ptr.getWrapPtr()->copyTo(m_data.m_wrapData.buff);
					}
				} else {
					if (ptr) {
						setWrapPtr(ptr.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
					} else {
						setWrapPtr(nullptr);
					}
				}
			}
			return *this;
		}

		constexpr AnyPtrT &operator=(AnyPtrT &&ptr) noexcept {
			if (this != &ptr) {
				if constexpr(unique) {
					free();
				} else if (std::is_constant_evaluated()) {
					ox::safeDelete(m_data.m_wrapPtr);
				}
				if (ptr) {
					setWrapPtr(ptr.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
					if (std::is_constant_evaluated()) {
						ox::safeDelete(ptr.m_data.m_wrapPtr);
						setWrapPtr(nullptr);
					}
				} else {
					m_data = {};
				}
			}
			return *this;
		}

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

		constexpr void free() noexcept {
			if (auto p = getWrapPtr()) {
				p->free();
			}
			if (std::is_constant_evaluated()) {
				ox::safeDelete(m_data.m_wrapPtr);
			}
			m_data.m_wrapData = {};
		}

		template<typename T>
		[[nodiscard]]
		constexpr T *get() const noexcept {
			if constexpr(defines::HasRTTI) {
				return dynamic_cast<Wrap<T> const*>(getWrapPtr())->data;
			}
			return static_cast<Wrap<T> const*>(getWrapPtr())->data;
		}

	private:
		constexpr void setWrapPtr(WrapBase *ptr) noexcept {
			if (std::is_constant_evaluated()) {
				m_data.m_wrapPtr = ptr;
			}
		}

		constexpr WrapBase *getWrapPtr() noexcept {
			if (std::is_constant_evaluated()) {
				return m_data.m_wrapPtr;
			} else {
				return std::launder(reinterpret_cast<WrapBase*>(m_data.m_wrapData.data()));
			}
		}

		constexpr WrapBase const *getWrapPtr() const noexcept {
			if (std::is_constant_evaluated()) {
				return m_data.m_wrapPtr;
			} else {
				return std::launder(reinterpret_cast<WrapBase const*>(m_data.m_wrapData.data()));
			}
		}

};

}

using AnyPtr = detail::AnyPtrT<false>;
using UAnyPtr = detail::AnyPtrT<true>;

}