#pragma once
#define _LUNA_SYSTEM_ERROR_EXTENSIONS
#include <luna/Check.h>
#include <luna/Move.h>
#include <luna/Option.h>
#include <luna/PlacementNew.h>
#include <luna/SystemError.h>
#include <luna/Types.h>

struct Error
{
    Error(int err)
    {
        error = err;
    }

    int error;
};

template <typename T> class Result
{
  public:
    Result(const T& value) : m_value(value)
    {
    }

    Result(T&& value) : m_value(move(value))
    {
    }

    Result(const Result<T>& other) : m_value(other.m_value), m_error(other.m_error)
    {
    }

    Result(Result<T>&& other) : m_value(move(other.m_value)), m_error(other.m_error)
    {
    }

    Result(const Error& err) : m_value(), m_error(err.error)
    {
    }

    Result<T>& operator=(const Result<T>& other)
    {
        if (this == &other) return *this;

        m_error = other.m_error;
        m_value = other.m_value;

        return *this;
    }

    Result<T>& operator=(Result<T>&& other)
    {
        if (this == &other) return *this;

        m_error = other.m_error;
        m_value = move(other.m_value);

        return *this;
    }

    bool has_error() const
    {
        return !m_value.has_value();
    }

    bool has_value() const
    {
        return m_value.has_value();
    }

    int error(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_error(), caller, "Result::error() called on a Result that holds a value");
        return m_error;
    }

    Error release_error(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_error(), caller, "Result::release_error() called on a Result that holds a value");
        return { m_error };
    }

    const char* error_string(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_error(), caller, "Result::error_string() called on a Result that holds a value");
        return ::error_string(m_error);
    }

    T value(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_value(), caller, "Result::value() called on a Result that holds an error");
        return m_value.unchecked_value({});
    }

    T expect_value(const char* reason, SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_value(), caller, reason);
        return m_value.unchecked_value({});
    }

    T value_or(const T& other) const
    {
        return m_value.value_or(other);
    }

    bool try_set_value(T& ref) const
    {
        return m_value.try_set_value(ref);
    }

    bool try_move_value(T& ref)
    {
        return m_value.try_move_value(ref);
    }

    T release_value(SourceLocation caller = SourceLocation::current())
    {
        expect_at(has_value(), caller, "Result::release_value() called on a Result that holds an error");
        return m_value.unchecked_release_value({});
    }

    T expect_release_value(const char* reason, SourceLocation caller = SourceLocation::current())
    {
        expect_at(has_value(), caller, reason);
        return m_value.unchecked_release_value({});
    }

    static Result<T> from_option(Option<T>&& option, int error)
    {
        if (option.has_value()) { return option.unchecked_release_value({}); }
        else
            return Error { error };
    }

  private:
    Option<T> m_value;
    int m_error;
};

template <> class Result<void>
{
  public:
    Result() : m_has_error(false)
    {
    }

    Result(const Result<void>& other) : m_has_error(other.m_has_error), m_error(other.m_error)
    {
    }

    Result(Result<void>&& other) : m_has_error(other.m_has_error), m_error(other.m_error)
    {
    }

    Result(const Error& err) : m_has_error(true), m_error(err.error)
    {
    }

    Result<void>& operator=(const Result<void>& other)
    {
        if (this == &other) return *this;

        m_has_error = other.m_has_error;
        m_error = other.m_error;

        return *this;
    }

    Result<void>& operator=(Result<void>&& other)
    {
        if (this == &other) return *this;

        m_has_error = other.m_has_error;
        m_error = other.m_error;

        return *this;
    }

    bool has_error() const
    {
        return m_has_error;
    }

    bool has_value() const
    {
        return !m_has_error;
    }

    int error(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_error(), caller, "Result::error() called on a Result that holds a value");
        return m_error;
    }

    Error release_error(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_error(), caller, "Result::release_error() called on a Result that holds a value");
        return { m_error };
    }

    const char* error_string(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_error(), caller, "Result::error_string() called on a Result that holds a value");
        return ::error_string(m_error);
    }

    void value(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_value(), caller, "Result::value() called on a Result that holds an error");
        return;
    }

    void expect_value(const char* reason, SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_value(), caller, reason);
        return;
    }

    void release_value(SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_value(), caller, "Result::release_value() called on a Result that holds an error");
        return;
    }

    void expect_release_value(const char* reason, SourceLocation caller = SourceLocation::current()) const
    {
        expect_at(has_value(), caller, reason);
        return;
    }

  private:
    bool m_has_error;
    int m_error;
};

// clang-format off
#define err(x) Error{x}
// clang-format on

#define TRY(expr)                                                                                                      \
    ({                                                                                                                 \
        auto _expr_rc = (expr);                                                                                        \
        if (!_expr_rc.has_value()) return _expr_rc.release_error();                                                    \
        _expr_rc.release_value();                                                                                      \
    })

template <typename T> inline Result<T*> nonnull_or_error(T* ptr, int error)
{
    if (ptr == nullptr) return err(error);
    else
        return ptr;
}