/*
 * 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

#if __has_include(<memory>)

#include <memory>

#else

#include "utility.hpp"

namespace std {

template<class T>
struct allocator {
	[[nodiscard]]
	constexpr T *allocate(size_t n) {
		 return static_cast<T*>(::operator new(n * sizeof(T)));
	}

	constexpr void deallocate(T *p, std::size_t) {
		 ::operator delete(p);
	}
};

template<typename T, typename... Args>
constexpr T *construct_at(T *p, Args &&...args ) {
	return new (p) T(ox::forward<Args>(args)...);
}

}

#endif

#include "error.hpp"
#include "utility.hpp"


namespace ox {

/*
 * safeDelete exists because deleting an incomplete type will simply
 * free the memory without running the destructor.
 */

template<typename T>
constexpr void safeDelete(T *val) requires(sizeof(T) >= 1) {
	delete val;
}

template<typename T>
constexpr void safeDeleteArray(T *val) requires(sizeof(T) >= 1) {
	delete[] val;
}

struct DefaultDelete {
	constexpr void operator()(auto *p) noexcept {
		safeDelete(p);
	}
};


template<typename T, typename Deleter = DefaultDelete>
class SharedPtr {

	private:
		T *m_t = nullptr;
		int *m_refCnt = nullptr;

	public:
		using value_type = T;
		explicit constexpr SharedPtr(T *t = nullptr) noexcept: m_t(t), m_refCnt(new int) {
		}

		constexpr SharedPtr(SharedPtr &other) {
			m_t = other.m_t;
			m_refCnt = other.m_refCnt;
			++*m_refCnt;
		}

		constexpr SharedPtr(const SharedPtr&) = delete;

		template<typename U>
		constexpr SharedPtr(SharedPtr<U> &&other) noexcept {
			m_t = other.m_t;
			m_refCnt = other.m_refCnt;
			other.m_refCnt = nullptr;
		}

		~SharedPtr() {
			if (m_refCnt) {
				--*m_refCnt;
				if (!*m_refCnt) {
					Deleter()(m_t);
					safeDelete(m_refCnt);
				}
			}
		}

		[[nodiscard]]
		constexpr T *get() const noexcept {
			return m_t;
		}

		constexpr T *reset() noexcept {
			if (m_refCnt) {
				--*m_refCnt;
				if (!*m_refCnt) {
					safeDelete(m_refCnt);
					Deleter()(m_t);
				}
			}
		}

		template<typename U>
		constexpr void reset(U *other) {
			reset();
			m_t = other;
			m_refCnt = new int(1);
		}

		template<typename U>
		constexpr SharedPtr &operator=(SharedPtr<U> &&other) {
			reset(std::move(other));
			return *this;
		}

		constexpr T *operator->() const noexcept {
			return m_t;
		}

		constexpr T &operator*() const noexcept {
			return *m_t;
		}

		constexpr operator bool() const noexcept {
			return m_t;
		}

};

template<typename T>
constexpr bool operator==(const SharedPtr<T> &p1, const SharedPtr<T> &p2) noexcept {
	return p1.get() == p2.get();
}

template<typename T>
constexpr bool operator==(const SharedPtr<T> &p1, std::nullptr_t) noexcept {
	return p1.get();
}

template<typename T>
constexpr bool operator==(std::nullptr_t, const SharedPtr<T> &p2) noexcept {
	return p2.get();
}


template<typename T>
constexpr bool operator!=(const SharedPtr<T> &p1, const SharedPtr<T> &p2) noexcept {
	return p1.get() != p2.get();
}

template<typename T>
constexpr bool operator!=(const SharedPtr<T> &p1, std::nullptr_t) noexcept {
	return !p1.get();
}

template<typename T>
constexpr bool operator!=(std::nullptr_t, const SharedPtr<T> &p2) noexcept {
	return !p2.get();
}


template<typename T, typename Deleter = DefaultDelete>
class UPtr {

	private:
		T *m_t = nullptr;

	public:
		using value_type = T;
		explicit constexpr UPtr(T *t = nullptr) noexcept: m_t(t) {
		}

		constexpr UPtr(UPtr&) = delete;

		constexpr UPtr(const UPtr&) = delete;

		template<typename U, typename UDeleter>
		constexpr UPtr(UPtr<U, UDeleter> &&other) noexcept {
			m_t = other.release();
		}

		constexpr ~UPtr() {
			Deleter()(m_t);
		}

		constexpr T *release() noexcept {
			auto t = m_t;
			m_t = nullptr;
			return t;
		}

		[[nodiscard]]
		constexpr T *get() const noexcept {
			return m_t;
		}

		constexpr void reset(UPtr &&other = UPtr()) {
			auto t = m_t;
			m_t = other.release();
			Deleter()(t);
		}

		constexpr UPtr &operator=(UPtr const&other) = delete;

		template<typename U, typename UDeleter>
		constexpr UPtr &operator=(UPtr<U, UDeleter> const&other) = delete;

		constexpr UPtr &operator=(UPtr<T> &&other) noexcept {
			reset(std::move(other));
			return *this;
		}

		template<typename U, typename UDeleter>
		constexpr UPtr &operator=(UPtr<U, UDeleter> &&other) noexcept {
			reset(std::move(other));
			return *this;
		}

		constexpr T *operator->() const noexcept {
			return m_t;
		}

		constexpr T &operator*() const noexcept {
			return *m_t;
		}

		constexpr operator bool() const noexcept {
			return m_t;
		}

};

template<typename T>
constexpr bool operator==(const UPtr<T> &p1, const UPtr<T> &p2) noexcept {
	return p1.get() == p2.get();
}

template<typename T>
constexpr bool operator==(const UPtr<T> &p1, std::nullptr_t) noexcept {
	return p1.get() == nullptr;
}

template<typename T>
constexpr bool operator==(std::nullptr_t, const UPtr<T> &p2) noexcept {
	return p2.get() == nullptr;
}


template<typename T>
constexpr bool operator!=(const UPtr<T> &p1, const UPtr<T> &p2) noexcept {
	return p1.get() != p2.get();
}

template<typename T>
constexpr bool operator!=(const UPtr<T> &p1, std::nullptr_t) noexcept {
	return !p1.get();
}

template<typename T>
constexpr bool operator!=(std::nullptr_t, const UPtr<T> &p2) noexcept {
	return !p2.get();
}


template<typename T, typename U = T, typename ...Args>
[[nodiscard]]
constexpr auto make_unique(Args&&... args) {
	return UPtr<U>(new T(ox::forward<Args>(args)...));
}

template<typename T, typename U = T, typename ...Args>
[[nodiscard]]
constexpr Result<UPtr<U>> make_unique_catch(Args&&... args) noexcept {
	try {
		return UPtr<U>(new T(ox::forward<Args>(args)...));
	} catch (ox::Exception const&ex) {
		return ex.toError();
	}
}

}
