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

namespace ox {

template<typename T, std::size_t buffSize = sizeof(T)>
class Optional {
	private:
		T *m_ptr = nullptr;
		AllocAlias<T> m_data = {};

	public:
		constexpr Optional() noexcept = default;

		template<typename ...Args>
		explicit constexpr Optional(ox::in_place_t, Args &&... args);

		constexpr Optional(const Optional &other) {
			if (other.m_ptr) {
				m_ptr = std::construct_at<T>(m_data.data(), *other.m_ptr);
			}
		}

		constexpr Optional(Optional &&other) noexcept {
			if (other.m_ptr) {
				m_ptr = std::construct_at<T>(m_data.data(), std::move(*other.m_ptr));
			}
		}

		constexpr ~Optional() {
			if (m_ptr) {
				reset();
			}
		}

		constexpr T &value() & noexcept {
			return *m_ptr;
		}

		constexpr const T &value() const & noexcept {
			return *m_ptr;
		}

		constexpr T &&value() && noexcept {
			return *m_ptr;
		}

		constexpr const T &&value() const && noexcept {
			return *m_ptr;
		}

		/**
		 * Returns parameter alt if Result contains an error.
		 * @param alt
		 * @return value of Result or alt
		 */
		constexpr T or_value(T &&alt) const& noexcept {
			if (!m_ptr) {
				return std::move(alt);
			}
			return *m_ptr;
		}

		/**
		 * Returns parameter alt if Result contains an error.
		 * @param alt
		 * @return value of Result or alt
		 */
		constexpr T or_value(T &&alt) && noexcept {
			if (!m_ptr) {
				return std::move(alt);
			}
			return std::move(*m_ptr);
		}

		/**
		 * Returns parameter alt if Result contains an error.
		 * @param alt
		 * @return value of Result or alt
		 */
		constexpr T or_value(T const&alt) const& noexcept {
			if (!m_ptr) {
				return alt;
			}
			return *m_ptr;
		}

		/**
		 * Returns parameter alt if Result contains an error.
		 * @param alt
		 * @return value of Result or alt
		 */
		constexpr T or_value(T const&alt) && noexcept {
			if (!m_ptr) {
				return alt;
			}
			return std::move(*m_ptr);
		}

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

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

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

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

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

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

		constexpr Optional &operator=(const Optional &other) {
			if (this == &other) {
				return *this;
			}
			if (other.m_ptr) {
				if (!m_ptr) {
					emplace();
				}
				*m_ptr = *other.m_ptr;
			} else {
				reset();
			}
			return *this;
		}

		constexpr Optional &operator=(Optional &&other) noexcept {
			if (this == &other) {
				return *this;
			}
			if (other.m_ptr) {
				if (!m_ptr) {
					emplace();
				}
				*m_ptr = std::move(*other.m_ptr);
			} else {
				reset();
			}
			return *this;
		}

		template<class... Args>
		constexpr T &emplace(Args &&...args) {
			reset();
			if (std::is_constant_evaluated()) {
				m_ptr = new T(ox::forward<Args>(args)...);
			} else {
				m_ptr = std::construct_at<T>(reinterpret_cast<T*>(m_data.data()), ox::forward<Args>(args)...);
			}
			return *m_ptr;
		}

		template<typename U, class... Args>
		constexpr T &emplace_subclass(Args &&...args) {
			static_assert(sizeof(U) <= buffSize, "Subclass is too large for this Optional");
			reset();
			if (std::is_constant_evaluated()) {
				m_ptr = new U(ox::forward<Args>(args)...);
			} else {
				m_ptr = std::construct_at<U>(reinterpret_cast<U*>(m_data.data()), ox::forward<Args>(args)...);
			}
			return *m_ptr;
		}

		[[nodiscard]]
		constexpr bool has_value() const noexcept {
			return m_ptr;
		}

		explicit constexpr operator bool() const noexcept {
			return m_ptr;
		}

		constexpr void reset() {
			if (std::is_constant_evaluated()) {
				ox::safeDelete(m_ptr);
			} else if (has_value()) {
				value().~T();
			}
			m_ptr = nullptr;
		}

};

template<typename T, std::size_t buffSize>
template<typename... Args>
constexpr Optional<T, buffSize>::Optional(ox::in_place_t, Args &&... args) {
	emplace(ox::forward<Args>(args)...);
}

}
