/*
	This file is part of Fennix Kernel.

	Fennix Kernel is free software: you can redistribute it and/or
	modify it under the terms of the GNU General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	Fennix Kernel is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <type_traits>
#include <functional>
#include <cstddef>
#include <utility>
#include <limits>
#include <new>

#include <debug.h>

namespace std
{
	namespace __memory__detail
	{
		template <class>
		constexpr bool is_unbounded_array_v = false;
		template <class T>
		constexpr bool is_unbounded_array_v<T[]> = true;

		template <class>
		constexpr bool is_bounded_array_v = false;
		template <class T, std::size_t N>
		constexpr bool is_bounded_array_v<T[N]> = true;
	}

	template <class T, class... Args>
	constexpr T *construct_at(T *p, Args &&...args)
	{
		return ::new (static_cast<void *>(p)) T(std::forward<Args>(args)...);
	}

	template <class T>
	constexpr void destroy_at(T *p)
	{
		p->~T();
	}

	template <class T>
	T *addressof(T &arg)
	{
		return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(arg)));
	}

	template <class T>
	const T *addressof(const T &&) = delete;

	template <class InputIt, class Size, class NoThrowForwardIt>
	NoThrowForwardIt uninitialized_copy_n(InputIt first, Size count, NoThrowForwardIt d_first)
	{
		using ValueType = typename std::iterator_traits<NoThrowForwardIt>::value_type;
		NoThrowForwardIt current = d_first;
		try
		{
			for (Size i = 0; i < count; ++i, (void)++current, ++first)
			{
				::new (static_cast<void *>(std::addressof(*current))) ValueType(*first);
			}
			return current;
		}
		catch (...)
		{
			for (; d_first != current; ++d_first)
			{
				d_first->~ValueType();
			}
			throw;
		}
	}

	template <class ExecutionPolicy, class ForwardIt, class Size, class NoThrowForwardIt>
	NoThrowForwardIt uninitialized_copy_n(ExecutionPolicy &&policy, ForwardIt first, Size count, NoThrowForwardIt d_first)
	{
		return uninitialized_copy_n(first, count, d_first);
	}

	template <class ForwardIt, class Size, class T>
	ForwardIt uninitialized_fill_n(ForwardIt first, Size count, const T &value)
	{
		using V = typename std::iterator_traits<ForwardIt>::value_type;
		ForwardIt current = first;
		try
		{
			for (; count > 0; ++current, (void)--count)
				::new (static_cast<void *>(std::addressof(*current))) V(value);
			return current;
		}
		catch (...)
		{
			for (; first != current; ++first)
				first->~V();
			throw;
		}
	}

	template <class ExecutionPolicy, class ForwardIt, class Size, class T>
	ForwardIt uninitialized_fill_n(ExecutionPolicy &&policy, ForwardIt first, Size count, const T &value)
	{
		return uninitialized_fill_n(first, count, value);
	}

	template <class Ptr>
	struct pointer_traits
	{
		using pointer = Ptr;
		using element_type = typename Ptr::element_type;
		using difference_type = typename Ptr::difference_type;

		template <class U>
		using rebind = typename Ptr::template rebind<U>;

		static pointer pointer_to(element_type &r) noexcept
		{
			return Ptr::pointer_to(r);
		}
	};

	template <class T>
	struct pointer_traits<T *>
	{
		using pointer = T *;
		using element_type = T;
		using difference_type = std::ptrdiff_t;

		template <class U>
		using rebind = U *;

		static pointer pointer_to(element_type &r) noexcept
		{
			return std::addressof(r);
		}
	};

	template <class Pointer, class SizeType = std::size_t>
	struct allocation_result
	{
		Pointer ptr;
		SizeType count;
	};

	template <class Alloc>
	struct allocator_traits
	{
		typedef Alloc allocator_type;
		typedef typename Alloc::value_type value_type;
		typedef typename Alloc::pointer pointer;
		typedef typename Alloc::const_pointer const_pointer;
		// typedef typename Alloc::void_pointer void_pointer;
		// typedef typename Alloc::const_void_pointer const_void_pointer;
		// typedef typename std::pointer_traits<pointer>::rebind<void> void_pointer;
		// typedef typename std::pointer_traits<pointer>::rebind<const void> const_void_pointer;
		typedef typename Alloc::difference_type difference_type;
		typedef typename Alloc::size_type size_type;
		// typedef typename Alloc::propagate_on_container_copy_assignment propagate_on_container_copy_assignment;
		typedef typename std::false_type propagate_on_container_copy_assignment;
		typedef typename Alloc::propagate_on_container_move_assignment propagate_on_container_move_assignment;
		typedef typename std::false_type propagate_on_container_swap;
		typedef typename Alloc::is_always_equal is_always_equal;

		template <class T>
		using rebind_alloc = typename Alloc::template rebind<T>::other;
		template <class T>
		using rebind_traits = allocator_traits<rebind_alloc<T>>;

		[[nodiscard]] static constexpr pointer allocate(Alloc &a, size_type n)
		{
			return a.allocate(n);
		}

		// [[nodiscard]] static constexpr pointer allocate(Alloc &a, size_type n, const_void_pointer hint)
		// {
		// 	return a.allocate(n, hint);
		// }

		[[nodiscard]] static constexpr std::allocation_result<pointer, size_type> allocate_at_least(Alloc &a, size_type n)
		{
			return a.allocate_at_least(n);
		}

		static constexpr void deallocate(Alloc &a, pointer p, size_type n)
		{
			a.deallocate(p, n);
		}

		template <class T, class... Args>
		static constexpr void construct(Alloc &a, T *p, Args &&...args)
		{
			std::construct_at(p, std::forward<Args>(args)...);
		}

		template <class T>
		static constexpr void destroy(Alloc &a, T *p)
		{
			std::destroy_at(p);
		}

		static constexpr size_type max_size(const Alloc &a)
		{
			return a.max_size();
		}

		static constexpr Alloc select_on_container_copy_construction(const Alloc &a)
		{
			return a;
		}
	};

	template <class T>
	struct allocator
	{
	public:
		typedef T value_type;
		typedef T *pointer;
		typedef const T *const_pointer;
		typedef T &reference;
		typedef const T &const_reference;
		typedef std::size_t size_type;
		typedef std::ptrdiff_t difference_type;
		typedef std::true_type propagate_on_container_move_assignment;
		typedef std::true_type is_always_equal;

		template <class U>
		struct rebind
		{
			typedef allocator<U> other;
		};

		allocator() {}
		allocator(const allocator &other) {}
		template <class U>
		allocator(const allocator<U> &other) {}
		~allocator() {}

		pointer allocate(size_type n, const void *hint = 0)
		{
			return static_cast<pointer>(::operator new(n * sizeof(T)));
		}

		std::allocation_result<T *, std::size_t> allocate_at_least(std::size_t n)
		{
			return {static_cast<T *>(::operator new(n * sizeof(T))), n};
		}

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

		pointer address(reference x) const { return &x; }
		const_pointer address(const_reference x) const { return &x; }
	};

	template <class T>
	struct default_delete
	{
		constexpr default_delete() noexcept = default;

		template <class U>
		constexpr default_delete(const default_delete<U> &d) noexcept {}

		constexpr void operator()(T *ptr) const { delete ptr; }
	};

	template <class T>
	struct default_delete<T[]>
	{
		constexpr default_delete() noexcept = default;

		template <class U>
		constexpr default_delete(const default_delete<U[]> &d) noexcept {}

		template <class U>
		constexpr void operator()(U *ptr) const { delete[] ptr; }
	};

	template <class T, class Deleter = std::default_delete<T>>
	class unique_ptr
	{
	public:
		using pointer = T *; // std::remove_reference<Deleter>::type::pointer;
		using element_type = T;
		using deleter_type = Deleter;

	private:
		pointer _ptr;

	public:
#pragma region Member Functions

		constexpr unique_ptr() noexcept : _ptr(nullptr) {}

		constexpr unique_ptr(std::nullptr_t) noexcept : _ptr(nullptr) {}

		constexpr explicit unique_ptr(pointer p) noexcept : _ptr(p) {}

		// constexpr unique_ptr(pointer p, /* TODO */ d1) noexcept : _ptr(p) {}

		// constexpr unique_ptr(pointer p, /* TODO */ d2) noexcept : _ptr(p) {}

		constexpr unique_ptr(unique_ptr &&u) noexcept : _ptr(u.release()) {}

		template <class U, class E>
		constexpr unique_ptr(unique_ptr<U, E> &&u) noexcept : _ptr(u.release()) {}

		unique_ptr(const unique_ptr &) = delete;

		~unique_ptr()
		{
			if (_ptr == nullptr)
				return;
			Deleter d;
			d(_ptr);
		}

		constexpr unique_ptr &operator=(unique_ptr &&r) noexcept
		{
			reset(r.release());
			return *this;
		}

		template <class U, class E>
		constexpr unique_ptr &operator=(unique_ptr<U, E> &&r) noexcept
		{
			reset(r.release());
			return *this;
		}

		constexpr unique_ptr &operator=(std::nullptr_t) noexcept
		{
			reset();
			return *this;
		}

		unique_ptr &operator=(const unique_ptr &) = delete;

#pragma endregion Member Functions

#pragma region Modifiers

		constexpr pointer release() noexcept
		{
			pointer p = _ptr;
			_ptr = nullptr;
			return p;
		}

		constexpr void reset(pointer ptr = pointer()) noexcept
		{
			Deleter d;
			d(_ptr);
			_ptr = ptr;
		}

		void swap(unique_ptr &other) noexcept
		{
			pointer tmp = _ptr;
			_ptr = other._ptr;
			other._ptr = tmp;
		}

#pragma endregion Modifiers

#pragma region Observers

		constexpr pointer get() const noexcept { return _ptr; }
		constexpr Deleter &get_deleter() noexcept { return _ptr; }
		constexpr const Deleter &get_deleter() const noexcept { return _ptr; }
		constexpr explicit operator bool() const noexcept { return get() != nullptr; }

#pragma endregion Observers

#pragma region Element Access

		constexpr typename std::add_lvalue_reference<T>::type operator*() const noexcept(noexcept(*std::declval<pointer>())) { return *_ptr; }
		constexpr pointer operator->() const noexcept { return _ptr; }

#pragma endregion Element Access
	};

	template <class T, class Deleter>
	class unique_ptr<T[], Deleter>
	{
	public:
		using pointer = T *; // std::remove_reference<Deleter>::type::pointer;
		using element_type = T;
		using deleter_type = Deleter;

	private:
		pointer _ptr;

	public:
#pragma region Member Functions

		constexpr unique_ptr() noexcept : _ptr(nullptr) {}

		constexpr unique_ptr(std::nullptr_t) noexcept : _ptr(nullptr) {}

		template <class U>
		constexpr explicit unique_ptr(U p) noexcept : _ptr(p) {}

		// template <class U>
		// constexpr unique_ptr(U p, /* TODO */ d1) noexcept : _ptr(p) {}

		// template <class U>
		// constexpr unique_ptr(U p, /* TODO */ d2) noexcept : _ptr(p) {}

		constexpr unique_ptr(unique_ptr &&u) noexcept : _ptr(u.release()) {}

		template <class U, class E>
		constexpr unique_ptr(unique_ptr<U, E> &&u) noexcept : _ptr(u.release()) {}

		unique_ptr(const unique_ptr &) = delete;

		~unique_ptr()
		{
			if (_ptr == nullptr)
				return;
			Deleter d;
			d(_ptr);
		}

		constexpr unique_ptr &operator=(unique_ptr &&r) noexcept
		{
			reset(r.release());
			return *this;
		}

		template <class U, class E>
		constexpr unique_ptr &operator=(unique_ptr<U, E> &&r) noexcept
		{
			reset(r.release());
			return *this;
		}

		constexpr unique_ptr &operator=(std::nullptr_t) noexcept
		{
			reset();
			return *this;
		}

		unique_ptr &operator=(const unique_ptr &) = delete;

#pragma endregion Member Functions

#pragma region Modifiers

		constexpr pointer release() noexcept
		{
			pointer p = _ptr;
			_ptr = nullptr;
			return p;
		}

		template <class U>
		constexpr void reset(U ptr) noexcept
		{
			Deleter d;
			d(_ptr);
			_ptr = ptr;
		}

		constexpr void reset(std::nullptr_t = nullptr) noexcept
		{
			Deleter d;
			d(_ptr);
			_ptr = nullptr;
		}

		void swap(unique_ptr &other) noexcept
		{
			pointer tmp = _ptr;
			_ptr = other._ptr;
			other._ptr = tmp;
		}

#pragma endregion Modifiers

#pragma region Observers

		constexpr pointer get() const noexcept { return _ptr; }
		constexpr Deleter &get_deleter() noexcept { return _ptr; }
		constexpr const Deleter &get_deleter() const noexcept { return _ptr; }
		constexpr explicit operator bool() const noexcept { return get() != nullptr; }

#pragma endregion Observers

#pragma region Element Access

		constexpr T &operator[](std::size_t i) const { return _ptr[i]; }

#pragma endregion Element Access
	};

	template <class T, class... Args>
	std::enable_if_t<!std::is_array<T>::value, std::unique_ptr<T>>
	make_unique(Args &&...args)
	{
		return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
	}

	template <class T>
	std::enable_if_t<__memory__detail::is_unbounded_array_v<T>, std::unique_ptr<T>>
	make_unique(std::size_t n)
	{
		return std::unique_ptr<T>(new std::remove_extent_t<T>[n]());
	}

	template <class T, class... Args>
	std::enable_if_t<__memory__detail::is_bounded_array_v<T>> make_unique(Args &&...) = delete;

	template <class T>
		requires(!std::is_array_v<T>)
	std::unique_ptr<T> make_unique_for_overwrite()
	{
		return std::unique_ptr<T>(new T);
	}

	template <class T>
		requires std::is_unbounded_array_v<T>
	std::unique_ptr<T> make_unique_for_overwrite(std::size_t n)
	{
		return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
	}

	template <class T, class... Args>
		requires std::is_bounded_array_v<T>
	void make_unique_for_overwrite(Args &&...) = delete;

	template <class T1, class D1, class T2, class D2>
	constexpr bool operator==(const unique_ptr<T1, D1> &x, const unique_ptr<T2, D2> &y) { return x.get() == y.get(); }

	template <class T1, class D1, class T2, class D2>
	bool operator<(const unique_ptr<T1, D1> &x, const unique_ptr<T2, D2> &y)
	{
		return std::less<typename std::common_type<typename unique_ptr<T1, D1>::pointer, typename unique_ptr<T2, D2>::pointer>::type>()(x.get(), y.get());
	}

	template <class T1, class D1, class T2, class D2>
	bool operator<=(const unique_ptr<T1, D1> &x, const unique_ptr<T2, D2> &y) { return !(y < x); }

	template <class T1, class D1, class T2, class D2>
	bool operator>(const unique_ptr<T1, D1> &x, const unique_ptr<T2, D2> &y) { return y < x; }

	template <class T1, class D1, class T2, class D2>
	bool operator>=(const unique_ptr<T1, D1> &x, const unique_ptr<T2, D2> &y) { return !(x < y); }

	// operator<=>(const unique_ptr<T1, D1> &x, const unique_ptr<T2, D2> &y);

	template <class T, class D>
	constexpr bool operator==(const unique_ptr<T, D> &x, std::nullptr_t) noexcept { return !x; }

	template <class T, class D>
	constexpr bool operator<(const unique_ptr<T, D> &x, std::nullptr_t) { return std::less<typename unique_ptr<T, D>::pointer>()(x.get(), nullptr); }

	template <class T, class D>
	constexpr bool operator<(std::nullptr_t, const unique_ptr<T, D> &y) { return std::less<typename unique_ptr<T, D>::pointer>()(nullptr, y.get()); }

	template <class T, class D>
	constexpr bool operator<=(const unique_ptr<T, D> &x, std::nullptr_t) { return !(nullptr < x); }

	template <class T, class D>
	constexpr bool operator<=(std::nullptr_t, const unique_ptr<T, D> &y) { return !(y < nullptr); }

	template <class T, class D>
	constexpr bool operator>(const unique_ptr<T, D> &x, std::nullptr_t) { return nullptr < x; }

	template <class T, class D>
	constexpr bool operator>(std::nullptr_t, const unique_ptr<T, D> &y) { return y < nullptr; }

	template <class T, class D>
	constexpr bool operator>=(const unique_ptr<T, D> &x, std::nullptr_t) { return !(x < nullptr); }

	template <class T, class D>
	constexpr bool operator>=(std::nullptr_t, const unique_ptr<T, D> &y) { return !(nullptr < y); }

	// operator<=>(const unique_ptr<T, D> &x, std::nullptr_t);

	// template <class CharT, class Traits, class Y, class D>
	// std::basic_ostream<CharT, Traits> &operator<<(std::basic_ostream<CharT, Traits> &os, const std::unique_ptr<Y, D> &p)
	// {
	// 	return os << p.get();
	// }

	template <class T, class D>
	void swap(std::unique_ptr<T, D> &lhs, std::unique_ptr<T, D> &rhs) noexcept
	{
		lhs.swap(rhs);
	}
}
