/*
 * Copyright 2015 - 2022 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 "bit.hpp"
#include "initializerlist.hpp"
#include "iterator.hpp"
#include "new.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;
		char m_data[buffSize] = {};

	public:
		constexpr Optional() noexcept = default;

		template<typename ...Args>
		constexpr Optional(Args &&... args);

		constexpr Optional(const Optional &other) {
			if (other.m_ptr) {
				m_ptr = new(m_data) T(*other.m_ptr);
			} else {
				m_ptr = nullptr;
			}
		}

		constexpr Optional(const Optional &&other) noexcept {
			if (m_ptr) {
				m_ptr->~T();
			}
			if (other.m_ptr) {
				m_ptr = new(m_data) T(std::move(*other.m_ptr));
			} else {
				m_ptr = nullptr;
			}
		}

		constexpr ~Optional() {
			if (m_ptr) {
				m_ptr->~T();
			}
		}

		template<typename U = T>
		constexpr U *get() noexcept {
			return m_ptr;
		}

		template<typename U = T>
		constexpr const U *get() 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 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 if (m_ptr) {
				m_ptr->~T();
				m_ptr = nullptr;
			}
			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 if (m_ptr) {
				m_ptr->~T();
				m_ptr = nullptr;
			}
			return *this;
		}

		template<class... Args>
		constexpr T &emplace(Args &&...args) {
			m_ptr = std::construct_at<T>(m_ptr, 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");
			m_ptr = std::construct_at<U>(m_ptr, 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() noexcept {
			get()->~T();
			m_ptr = nullptr;
		}

};

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

}