/*
 * 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(<exception>)
#include <exception>
#else
namespace std {
class exception {
	public:
		virtual ~exception() = default;

		[[nodiscard]]
		virtual char const *what() const noexcept {
			return "";
		}
};
}
#endif

#include "defines.hpp"
#include "def.hpp"
#include "source_location.hpp"
#include "typetraits.hpp"
#include "utility.hpp"

namespace ox {

using ErrorCode = uint16_t;


struct [[nodiscard]] Error {
	std::source_location src;
	CString msg = nullptr;
	ErrorCode errCode = 0;

	constexpr Error() noexcept = default;

	explicit constexpr Error(
		ErrorCode const errCode,
		std::source_location const &src = std::source_location::current()) noexcept:
		src{src},
		errCode{errCode}
	{}

	explicit constexpr Error(
		ErrorCode const errCode,
		CString const msg,
		std::source_location const &src = std::source_location::current()) noexcept:
		src{src},
		msg{msg},
		errCode{errCode}
	{}

	constexpr operator uint64_t() const noexcept {
		return errCode;
	}

	/**
	 * Checks the Error to see if is not OK, and throws the Error as an ox::Exception if not.
	 */
	constexpr void checkThrow() const;

	constexpr Error reoriginate(
		ErrorCode const pErrCode,
		CString const pMsg = nullptr,
		std::source_location const &pSrc = std::source_location::current()) const noexcept {
		if (errCode) {
			return Error{pErrCode, pMsg, pSrc};
		}
		return Error{};
	}

	constexpr Error reoriginate(
		std::source_location const &pSrc = std::source_location::current()) const noexcept {
		return Error{errCode, msg, pSrc};
	}

};

[[nodiscard]]
constexpr auto errCode(Error const &err) noexcept {
	return err.errCode;
}

template<typename T = char const*>
[[nodiscard]]
constexpr auto toStr(Error const &err) noexcept {
	return err.msg ? T{err.msg} : "";
}

struct Exception: public std::exception {
	std::source_location src;
	CString msg = nullptr;
	ErrorCode errCode = 0;

	explicit Exception(
		ErrorCode const errCode,
		std::source_location const &src = std::source_location::current()) noexcept:
		src{src},
		errCode{errCode} {}

	explicit Exception(
		ErrorCode const errCode,
		CString const msg,
		std::source_location const &src = std::source_location::current()) noexcept:
		src{src},
		msg{msg},
		errCode{errCode} {}

	explicit Exception(Error const &err) noexcept:
		src{err.src},
		msg{err.msg ? err.msg : ""},
		errCode{err.errCode} {}

	constexpr Error toError() const noexcept {
		return Error(errCode, msg, src);
	}

	[[nodiscard]]
	char const *what() const noexcept override {
		return msg;
	}
};


constexpr void Error::checkThrow() const {
	if (errCode) [[unlikely]] {
		throw Exception{*this};
	}
}


[[noreturn]]
void panic(
	Error const &err,
	CString panicMsg,
	std::source_location const &src = std::source_location::current()) noexcept;

template<typename T>
struct [[nodiscard]] Result {

	using type = remove_reference_t<T>;

	T value;
	Error error;

	constexpr Result() noexcept: value(), error(0) {
	}

	template<typename U>
	constexpr Result(Result<U> const &other) noexcept: value(other.value), error(other.error) {
	}

	template<typename U>
	constexpr Result(Result<U> &&other) noexcept: value(std::move(other.value)), error(std::move(other.error)) {
	}

	constexpr Result(Error const &error) noexcept: value(), error(error) {
	}

	constexpr Result(type const &value, Error const &error = {}) noexcept: value(value), error(error) {
	}

	constexpr Result(type &&value, Error const &error = {}) noexcept: value(std::move(value)), error(error) {
	}

	constexpr ~Result() noexcept = default;

	explicit constexpr operator type const &() const noexcept {
		return value;
	}

	explicit constexpr operator type&() noexcept {
		return value;
	}

	[[nodiscard]]
	constexpr bool ok() const noexcept {
		return error == 0;
	}

	template<typename U>
	constexpr Error copyTo(U &val) const & noexcept {
		if (!error) [[likely]] {
			val = value;
		}
		return error;
	}

	template<typename U>
	constexpr Error copyTo(U &val) && noexcept {
		if (!error) [[likely]] {
			val = std::move(value);
		}
		return error;
	}

	template<typename U>
	constexpr Error moveTo(U &val) noexcept {
		if (!error) [[likely]] {
			val = std::move(value);
		}
		return error;
	}

	[[nodiscard]]
	constexpr T &unwrap() & noexcept {
		if (error) {
			ox::panic(error, "Failed unwrap");
		}
		return value;
	}

	[[nodiscard]]
	constexpr T &&unwrap() && noexcept {
		if (error) {
			ox::panic(error, "Failed unwrap");
		}
		return std::move(value);
	}

	[[nodiscard]]
	constexpr T const &unwrap() const & noexcept {
		if (error) [[unlikely]] {
			ox::panic(error, "Failed unwrap");
		}
		return value;
	}

	[[nodiscard]]
	constexpr T &unwrapThrow() & {
		if (error) {
			throw Exception(error);
		}
		return value;
	}

	[[nodiscard]]
	constexpr T &&unwrapThrow() && {
		if (error) {
			throw Exception(error);
		}
		return std::move(value);
	}

	[[nodiscard]]
	constexpr T const &unwrapThrow() const & {
		if (error) {
			throw ox::Exception(error);
		}
		return value;
	}

	template<typename U = T>
	constexpr ox::Result<U> to() & noexcept {
		if (error) [[unlikely]] {
			return error;
		}
		return U(value);
	}

	template<typename U = T>
	constexpr ox::Result<U> to() && noexcept {
		if (error) [[unlikely]] {
			return error;
		}
		return U(std::move(value));
	}

	template<typename U = T>
	constexpr ox::Result<U> to(auto const &f) & noexcept {
		if (error) [[unlikely]] {
			return error;
		}
		return f(value);
	}

	template<typename U = T>
	constexpr ox::Result<U> to(auto const &f) && noexcept {
		if (error) [[unlikely]] {
			return error;
		}
		return f(std::move(value));
	}

	/**
	 * 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 (error) {
			return std::move(alt);
		}
		return value;
	}

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

	/**
	 * 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 (error) {
			return alt;
		}
		return value;
	}

	/**
	 * 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 (error) {
			return alt;
		}
		return std::move(value);
	}

	constexpr Result transformError(ErrorCode const ec, CString const msg) && {
		if (error) {
			error = Error{ec, msg};
		}
		return *this;
	}

	constexpr Result reoriginate(
		ErrorCode const pErrCode,
		CString const pMsg = nullptr,
		std::source_location const &pSrc = std::source_location::current()) && noexcept {
		if (error) {
			return {std::move(value), Error{pErrCode, pMsg, pSrc}};
		}
		return {std::move(value)};
	}

	constexpr Result reoriginate(
		std::source_location const &pSrc = std::source_location::current()) && noexcept {
		return {std::move(value), Error{error.errCode, error.msg, pSrc}};
	}

};

namespace detail {

constexpr Error toError(Error const &e) noexcept {
	return e;
}

template<typename T>
constexpr Error toError(Result<T> const &r) noexcept {
	return r.error;
}

}

constexpr void primitiveAssert(
	bool const pass,
	char const *msg,
	std::source_location const &src = std::source_location::current()) noexcept {
	if constexpr(ox::defines::Debug) {
		if (!pass) [[unlikely]] {
			panic(ox::Error{1}, msg, src);
		}
	}
}

constexpr void boundsCheck(
	size_t const i,
	size_t const sz,
	char const *msg,
	std::source_location const &src = std::source_location::current()) noexcept {
	if constexpr(defines::CheckBounds) {
		if (i >= sz) [[unlikely]] {
			panic(ox::Error{1}, msg, src);
		}
	}
}

}
