/*
 * 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

#if __has_include(<exception>)
#include <exception>
#else
namespace std {
class exception {
	public:
		virtual ~exception() = default;

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

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

#define OxError(...) ox::Error(__FILE__, __LINE__, __VA_ARGS__)
#define OxException(...) ox::Exception(__FILE__, __LINE__, __VA_ARGS__)

namespace ox {

using ErrorCode = uint16_t;


struct [[nodiscard]] Error {
	const char *msg = nullptr;
	const char *file = nullptr;
	uint16_t line = 0;
	ErrorCode errCode = 0;

	constexpr Error() noexcept = default;

	explicit constexpr Error(ErrorCode ec) noexcept: errCode(ec) {
	}

	explicit constexpr Error(const char *file, uint32_t line, ErrorCode errCode, const char *msg = nullptr) noexcept {
		this->file = file;
		this->line = line;
		this->msg = msg;
		this->errCode = errCode;
	}

	constexpr Error(const Error &o) noexcept {
		this->msg = o.msg;
		this->file = o.file;
		this->line = o.line;
		this->errCode = o.errCode;
	}

	constexpr Error &operator=(const Error &o) noexcept {
		this->msg = o.msg;
		this->file = o.file;
		this->line = o.line;
		this->errCode = o.errCode;
		return *this;
	}

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

};

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

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

struct Exception: public std::exception {
	const char *msg = nullptr;
	const char *file = nullptr;
	uint16_t line = 0;
	ErrorCode errCode = 0;

	explicit inline Exception(const char *file, uint32_t line, ErrorCode errCode, const char *msg = "") noexcept {
		this->file = file;
		this->line = line;
		this->msg = msg;
		this->errCode = errCode;
	}

	inline explicit Exception(const Error &err) {
		if (err.msg) {
			this->msg = err.msg;
		} else {
			this->msg = "";
		}
		this->file = err.file;
		this->line = err.line;
		this->errCode = err.errCode;
	}

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

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

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

	using type = typename remove_reference<T>::type;

	T value;
	Error error;

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

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

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

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

	constexpr Result(const type &value, const Error &error = OxError(0)) noexcept: value(const_cast<type&>(value)), error(error) {
	}

	constexpr Result(type &&value, const Error &error = OxError(0)) noexcept: value(ox::forward<type>(value)), error(error) {
	}

	constexpr ~Result() noexcept = default;

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

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

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

	constexpr Error get(type *val) const noexcept {
		*val = value;
		return error;
	}

	constexpr Error get(type *val) noexcept {
		*val = value;
		return error;
	}

	constexpr Error moveTo(type *val) noexcept {
		if (!error) [[likely]] {
			*val = std::move(value);
		}
		return error;
	}

};

namespace detail {

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

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

}

}

constexpr void oxIgnoreError(const ox::Error&) noexcept {}
template<typename T>
constexpr void oxIgnoreError(const ox::Result<T>&) noexcept {}
