/*
 * 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 "error.hpp"
#include "defines.hpp"
#include "types.hpp"
#include "utility.hpp"

#if defined(_MSC_VER)
#include <malloc.h>
#elif OX_USE_STDLIB
#include <cstdlib>
#endif


#if __has_include(<new>)
#include <new>
#else
constexpr void *operator new(std::size_t, void *addr) noexcept {
	return addr;
}

constexpr void *operator new[](std::size_t, void *addr) noexcept {
	return addr;
}

namespace std {

template<typename T>
[[nodiscard]]
constexpr T* launder(T* __p) noexcept {
	return __builtin_launder(__p);
}

template<typename T, typename... Args, bool noex>
void launder(T(*)(Args...) noexcept(noex)) = delete;
template<typename T, typename... Args, bool noex>
void launder(T(*)(Args......) noexcept(noex)) = delete;
void launder(void*) = delete;
void launder(void const*) = delete;
void launder(volatile void*) = delete;
void launder(volatile void const*) = delete;

}
#endif


namespace ox {

/**
 * Aliases type T in size and alignment to allow allocating space for a T
 * without running the constructor.
 */
template<typename T, std::size_t sz = sizeof(T)>
struct alignas(alignof(T)) AllocAlias {
	char buff[sz];
	constexpr AllocAlias() noexcept = default;
	[[nodiscard]]
	auto data() noexcept {
		return reinterpret_cast<T*>(this);
	}
	[[nodiscard]]
	auto data() const noexcept {
		return reinterpret_cast<T const*>(this);
	}
};


template<typename T, typename U = T, typename ...Args>
[[nodiscard]]
constexpr U *make(Args &&...args) noexcept {
#ifdef __cpp_exceptions
	try {
		return new T(ox::forward<Args>(args)...);
	} catch (std::exception const &ex) {
		ox::panic(ox::Error(1, ex.what()), ex.what());
		return nullptr;
	} catch (...) {
		ox::panic(ox::Error(2, "Allocation or constructor failed"),  "Allocation or constructor failed");
		return nullptr;
	}
#else
	return new T(ox::forward<Args>(args)...);
#endif
}

template<typename T, typename ...Args>
[[nodiscard]]
constexpr Result<T*> makeCatch(Args &&...args) noexcept {
#ifdef __cpp_exceptions
	try {
		return new T(ox::forward<Args>(args)...);
	} catch (const ox::Exception &ex) {
		return ex.toError();
	} catch (...) {
		return ox::Error(1, "Allocation or constructor failed");
	}
#else
	return new T(ox::forward<Args>(args)...);
#endif
}

constexpr auto MallocaStackLimit = defines::UseStdLib ? 1024 : 0;

/**
 * MallocaPtr will automatically cleanup the pointed to address upon
 * destruction if the size of the allocation is greater than MallocaStackLimit.
 */
template<typename T>
class MallocaPtr {

	private:
		bool m_onHeap = false;
		T *m_val = nullptr;

	public:
		constexpr MallocaPtr() noexcept = default;

		constexpr MallocaPtr(MallocaPtr &other) = delete;

		constexpr MallocaPtr(const MallocaPtr &other) = delete;

		constexpr MallocaPtr(MallocaPtr &&other) noexcept {
			m_onHeap = other.m_onHeap;
			m_val = other.m_val;
			other.m_onHeap = false;
			other.m_val = nullptr;
		}

		constexpr MallocaPtr(bool onHeap, T *val) noexcept {
			m_onHeap = onHeap;
			m_val = val;
		}

		constexpr ~MallocaPtr() noexcept {
			m_val->~T();
			if (m_onHeap && m_val) {
				delete[] reinterpret_cast<uint8_t*>(m_val);
			}
		}

		constexpr const T *get() const noexcept {
			return reinterpret_cast<T*>(m_val);
		}

		constexpr T *get() noexcept {
			return reinterpret_cast<T*>(m_val);
		}

		constexpr MallocaPtr &operator=(MallocaPtr &other) = delete;

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

		constexpr MallocaPtr &operator=(MallocaPtr &&other) noexcept {
			if (m_onHeap && m_val) {
				delete[] reinterpret_cast<uint8_t*>(m_val);
			}
			m_onHeap = other.m_onHeap;
			m_val = other.m_val;
			other.m_onHeap = false;
			other.m_val = nullptr;
			return *this;
		}

		constexpr const T *operator->() const noexcept {
			return reinterpret_cast<T*>(m_val);
		}

		constexpr T *operator->() noexcept {
			return reinterpret_cast<T*>(m_val);
		}

		constexpr explicit operator const T*() const noexcept {
			return reinterpret_cast<T*>(m_val);
		}

		constexpr explicit operator T*() noexcept {
			return reinterpret_cast<T*>(m_val);
		}

		constexpr const T &operator*() const noexcept {
			return *reinterpret_cast<T*>(m_val);
		}

		constexpr T &operator*() noexcept {
			return *reinterpret_cast<T*>(m_val);
		}

		constexpr bool operator==(std::nullptr_t) const noexcept {
			return m_val == nullptr;
		}

		constexpr bool operator==(const MallocaPtr<T> &other) const noexcept {
			return m_val == other.m_val && m_onHeap == other.m_onHeap;
		}

		constexpr bool operator!=(const MallocaPtr<T> &other) const noexcept {
			return m_val != other.m_val || m_onHeap != other.m_onHeap;
		}

};

}
