#pragma once

#include <util/generic/overloaded.h>

#include <util/system/yassert.h>
#include <util/generic/ptr.h>
#include <util/generic/variant.h>
#include <util/generic/vector.h>
#include <util/stream/str.h>

#include <variant>

#define Y_VAR(VAL) VAL ## __LINE__

/**
 * A try/catch alternative that can handle TError.
 *
 * Usage example:
 * Y_TRY(TError, error) {
 *     Y_PROPAGATE_ERROR(foo());
 *     Y_PROPAGATE_ERROR(goo());
 *     return boo();
 * } Y_CATCH {
 *     if (error.GetAs<TForceStreamClose>()) {
 *         RegisterSuccess(data, matched);
 *     } else {
 *         RegisterFailure(data, matched);
 *     }
 *     return error;
 * }
 **/
#define Y_TRY(ERR, VAR) \
    if (auto VAR = [&]() noexcept -> ERR

#define Y_CATCH \
    ())

/**
 * Turns the catched exception into a TError that contains the first type from the list of types the exception can be casted to or
 * yexception if there is no such type.
 *
 * Usage example:
 * try {
 *     f1(); // may throw THttpParseError, THttpError, yexception.
 * } Y_TRY_STORE(THttpParseError, THttpError) // will return appropriate TError here.
 *
 * Note that the exception will be casted to the FIRST suitable type,
 * so you want to order types in TError from children to their parents.
 **/
#define Y_TRY_STORE(...)                                                    \
    catch (std::exception& e) {                                             \
        return ::NSrvKernel::TryStoreException<__VA_ARGS__>(e);             \
    } catch (...) {                                                         \
        return Y_MAKE_ERROR(yexception{} << "Not exception thrown");        \
    }

/**
 * Returns with TError if the corresponding expression returns a non-empty TError. *
 *
 * Usage example:
 * Y_PROPAGATE_ERROR(encoder.SendEof());
 **/
#define Y_PROPAGATE_ERROR(EXP)      \
    if (auto Y_VAR(_err) = (EXP)) { \
        return Y_VAR(_err);         \
    }

/**
 * Creates a TError wrapping around the error.
 *
 * Usage example:
 * if (!mod) {
 *     LOG_ERROR(TLOG_ERR, descr, "no module for request handling");
 *     return Y_MAKE_ERROR(THttpError{404} << "no module for request handling");
 * }
 **/
#define Y_MAKE_ERROR(ERROR) \
    ::NSrvKernel::MakeError<std::decay_t<decltype(ERROR)>>(__LOCATION__ + ERROR)

/**
 * Y_ENSURE analogue that returns a TError when the condition is unsatisfied.
 *
 * Usage example:
 * Y_REQUIRE(!!in,
 *           TIncompleteMessage{} << "got EOF while parsing message length");
 **/
#define Y_REQUIRE(EXP, ERROR)       \
    if (!(EXP)) {                   \
        return Y_MAKE_ERROR(ERROR); \
    }

namespace NNoexcept { // TODO(tender-bum): better naming

namespace NPrivate {

struct TErrorAccessor;

template <class T, size_t I>
struct TIndexedType {};

template <class T, size_t I, class... Ts, size_t... Is>
constexpr bool IsSame(TIndexedType<T, I>, TIndexedType<Ts, Is>...) {
    return (... && (I != Is && std::is_same_v<T, Ts>));
}

template <class... Ts, size_t... Is>
constexpr bool IsDistinct(std::index_sequence<Is...>) {
    return !(... || IsSame(TIndexedType<Ts, Is>{}, TIndexedType<Ts, Is>{}...));
}

template <class... Ts>
struct TTypePack {};

template <class U, class... Es>
constexpr bool IsPresent() {
    return (... || std::is_same_v<U, Es>);
}

template <class... Superset>
struct TIsSubset {
    template <class... Subset>
    static constexpr bool Apply() {
        return (... && IsPresent<Subset, Superset...>());
    }
};

}  // namespace NPrivate

/**
 * An exception proxy value.
 * Can be constructed using MakeError.
 **/
template <class... Es>
class [[nodiscard]] TError {
    static_assert((... && std::is_base_of_v<std::exception, Es>), // TODO(tender-bum): lower this restriction
                  "All members of TError must be descendants of std::exception");

    static_assert(NPrivate::IsDistinct<Es...>(std::index_sequence_for<Es...>{}),
                  "All members of TError must be distinct");

public:
    TError() = default;

    TError(TError&& rhs) noexcept
        : Error_(std::move(rhs.Error_))
    {
        rhs.Error_ = std::monostate{};
    }

    template <class... Us,
              class = std::enable_if_t<NPrivate::TIsSubset<Es...>::template Apply<Us...>()>>
    TError(TError<Us...>&& rhs) noexcept {
        std::visit(TOverloaded{
            [this](auto& v) { Error_ = std::move(v); },
            [](std::monostate&) {}
        }, rhs.Error_);
        rhs.Error_ = std::monostate{};
    }

    TError& operator=(TError&& rhs) noexcept {
        TError{std::move(rhs)}.swap(*this);
        return *this;
    }

    template <class... Us>
    std::enable_if_t<NPrivate::TIsSubset<Es...>::template Apply<Us...>(),
    TError&> operator=(TError<Us...>&& rhs) noexcept {
        TError{std::move(rhs)}.swap(*this);
        return *this;
    }

    TError Clone() {
        return std::visit(TOverloaded{
                [](std::monostate) -> TError { return TError{}; },
                [](auto error) -> TError {
                    TError tmp;
                    tmp.Error_.template emplace<decltype(error)>(error);
                    return tmp;
                },
        }, Error_);
    }

    void swap(TError& rhs){
        DoSwap(Error_, rhs.Error_);
    }

    /**
     * Checks whether TError holds an error or empty.
     * Default constructed TError is empty.
     **/
    explicit constexpr operator bool() const & noexcept {
        return !std::holds_alternative<std::monostate>(Error_);
    }

    constexpr operator bool() const && noexcept = delete;

    /**
     * Accesses the underlying error by a std::exception reference.
     * The behavior is undefined if TError is empty.
     **/
    std::exception& operator*() noexcept {
        return const_cast<std::exception&>(const_cast<const TError*>(this)->operator*());
    }

    /**
     * Accesses the underlying error via a std::exception reference.
     * The behavior is undefined if TError is empty.
     **/
    const std::exception& operator*() const noexcept {
        return std::visit(TOverloaded{
            [](std::monostate) -> const std::exception& { Y_FAIL(); },
            [](auto& error) -> const std::exception& { return error; },
        }, Error_);
    }

    /**
     * Accesses the underlying error via a std::exception pointer.
     * The behavior is undefined if TError is empty.
     **/
    std::exception* operator->() noexcept {
        return &(this->operator*());
    }

    /**
     * Accesses the underlying error via a std::exception pointer.
     * The behavior is undefined if TError is empty.
     **/
    const std::exception* operator->() const noexcept {
        return &(this->operator*());
    }

    /**
     * Tries to get the underlying error downcasted to the specified type.
     * E must be a descendant of std::exception.
     * The behavior is undefined if TError is empty.
     **/
    template <class E>
    [[nodiscard]] std::enable_if_t<std::is_base_of_v<std::exception, E>,
    E*> GetAs() noexcept {
        return const_cast<E*>((const_cast<const TError*>(this))->GetAs<E>());
    }

    /**
     * Tries to get the underlying error downcasted to the specified type.
     * E must be a descendant of std::exception.
     * The behavior is undefined if TError is empty.
     **/
    template <class E>
    [[nodiscard]] std::enable_if_t<std::is_base_of_v<std::exception, E>,
    const E*> GetAs() const noexcept {
        return std::visit([](auto& val) -> const E* {
            using T = std::decay_t<decltype(val)>;
            if constexpr (std::is_base_of_v<E, T>) {
                return &val;
            } else if constexpr (std::is_same_v<std::monostate, T>) {
                Y_FAIL();
            } else {
                return nullptr;
            }
        }, Error_);
    }

    /**
     * Throws the underlying error thus transforming TError back to exception.
     * TError then becomes empty.
     * The behavior is undefined if TError is empty.
     **/
    [[noreturn]] void Throw() {
        std::visit(TOverloaded{
            [](std::monostate) { Y_FAIL(); },
            [](auto&& error) { throw std::move(error); },
        }, std::exchange(Error_, std::monostate{}));
        Y_FAIL();
    }

private:
    friend struct NPrivate::TErrorAccessor;
    template <class... Us>
    friend class TError;

    // Do not change the order!
    std::variant<std::monostate, Es...> Error_;
};

namespace NPrivate {

struct TErrorAccessor {
    template <class... Es>
    static auto& GetInnerVariant(TError<Es...>& error) noexcept {
        return error.Error_;
    }
};

}  // namespace NPrivate

/**
 * Represents either TError or T. Usually used as a function return value.
 **/
template <class T, class E>
class [[nodiscard]] TErrorOr;

template <class T, class... Es>
class [[nodiscard]] TErrorOr<T, TError<Es...>> {
    static_assert(!::TIsSpecializationOf<TError, T>::value);
    static_assert(!::TIsSpecializationOf<TErrorOr, T>::value);

public:
    using value_type = T;
    using error_type = TError<Es...>;

public:
    TErrorOr(error_type&& error) noexcept
        : Value_(std::in_place_type<error_type>, std::move(error))
    {}

    template <class... Us,
              class = std::enable_if_t<std::is_constructible_v<error_type, TError<Us...>>>>
    TErrorOr(TError<Us...>&& error) noexcept
        : Value_(std::in_place_type<error_type>, std::move(error))
    {}

    template <class... Args, class = std::enable_if_t<std::is_constructible_v<T, Args...>>>
    TErrorOr(Args&&... args) noexcept {
        Value_.template emplace<T>(std::forward<Args>(args)...);
    }

    TErrorOr(TErrorOr&&) noexcept = default;
    TErrorOr& operator=(TErrorOr&&) noexcept = default;

    /**
     * Checks whether TErrorOr represents TError.
     **/
    explicit operator bool() const & noexcept {
        return std::holds_alternative<error_type>(Value_);
    }
    operator bool() const && noexcept = delete;

    /**
     * Assigns the underlying value of type T to dst.
     * Returns TError if TErrorOr represents a TError.
     **/
    template <class U = T>
    error_type AssignTo(U& dst) noexcept {
        return Apply([&dst](T& v) {
            dst = std::move(v);
        });
    }

    /**
     * Applies a function to the underlying value of type T.
     * Returns TError if TErrorOr represents TError.
     **/
    template <class F>
    error_type Apply(F&& f) noexcept {
        return std::visit(TOverloaded{
            [](error_type& v) { return std::move(v); },
            [&f](T& v) { f(v); return error_type(); }
        }, Value_);
    }

    /**
     * Returns the underlying TError. Returns empty TError if TErrorOr represents T.
     **/
    error_type ReleaseError() noexcept {
        return std::visit(TOverloaded{
            [](error_type& v)  { return std::move(v); },
            [](T&) -> error_type { return error_type(); }
        }, Value_);
    };

    /**
     * Returns the underlying value or throws the holded exception.
     */
    T& GetOrThrow() {
        return std::visit(TOverloaded{
            [](error_type& v) ->T& { v.Throw(); },
            [](T& t) -> T& { return t; }
        }, Value_);
    }

private:
    std::variant<error_type, T> Value_;
};

}  // namespace NNoexcept

namespace NSrvKernel {

/**
 * Gets the error message from TError.
 * The behavior is undefined if TError does not hold an error.
 **/
template <class... Es>
TString GetErrorMessage(const NNoexcept::TError<Es...>& error) noexcept {
    Y_ASSERT(error);

    TStringStream message;
    message << "(" << TypeName(*error) << ") " << error->what();
    return message.Str();
}

/**
 * Throws the error from provided TError. Does nothing if TError is empty.
 **/
template <class... Es>
void TryRethrowError(NNoexcept::TError<Es...> st) {
    if (st) {
        st.Throw();
    }
}

/**
 * Creates TError with error of type E.
 **/
template <class E, class... Args>
NNoexcept::TError<E> MakeError(Args&&... args) noexcept {
    NNoexcept::TError<E> result;
    NNoexcept::NPrivate::TErrorAccessor::GetInnerVariant(result).template emplace<E>(
        std::forward<Args>(args)...);
    return result;
}

/**
 * Wraps the std::exception in TError of its type or yexception if no types matched.
 **/
template <class T, class... Ts>
NNoexcept::TError<T, Ts...> TryStoreException(std::exception& e) {
    if (auto* exc = dynamic_cast<T*>(&e)) {
        return MakeError<T>(std::move(*exc));
    }
    if constexpr (sizeof...(Ts) > 0){
        return TryStoreException<Ts...>(e) ;
    } else {
        return MakeError<yexception>(yexception{} << e.what());
    }
}

}  // namespace NSrvKernel
