#include "errors.h"

#include <util/generic/algorithm.h>
#include <util/generic/strbuf.h>
#include <util/stream/output.h>

#include <errno.h>

using namespace NSrvKernel;

namespace {
    // TODO(velavokr): update the errno list from http://man7.org/linux/man-pages/man3/errno.3.html ,
    // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
    // and
    // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
    class TErrnoOutHelper {
    public:
        TErrnoOutHelper() noexcept {
            Fill(&E_[0], &E_[Y_ARRAY_SIZE(E_)], TStringBuf());
#define INIT(E) if ((E) >= Y_ARRAY_SIZE(E_)); else E_[E] = TStringBuf(#E);
            INIT(EPERM)
            INIT(ENOENT)
            INIT(ESRCH)
            INIT(EINTR)
            INIT(EIO)
            INIT(ENXIO)
            INIT(E2BIG)
            INIT(ENOEXEC)
            INIT(EBADF)
            INIT(ECHILD)
            INIT(EDEADLK)
            INIT(ENOMEM)
            INIT(EACCES)
            INIT(EFAULT)
#ifdef ENOTBLK
            INIT(ENOTBLK)
#endif
            INIT(EBUSY)
            INIT(EEXIST)
            INIT(EXDEV)
            INIT(ENODEV)
            INIT(ENOTDIR)
            INIT(EISDIR)
            INIT(EINVAL)
            INIT(ENFILE)
            INIT(EMFILE)
            INIT(ENOTTY)
            INIT(ETXTBSY)
            INIT(EFBIG)
            INIT(ENOSPC)
            INIT(ESPIPE)
            INIT(EROFS)
            INIT(EMLINK)
            INIT(EPIPE)
            INIT(EDOM)
            INIT(ERANGE)
            INIT(EAGAIN)
            INIT(EINPROGRESS)
            INIT(EALREADY)
            INIT(ENOTSOCK)
            INIT(EDESTADDRREQ)
            INIT(EMSGSIZE)
            INIT(EPROTOTYPE)
            INIT(ENOPROTOOPT)
            INIT(EPROTONOSUPPORT)
            INIT(ESOCKTNOSUPPORT)
            INIT(EOPNOTSUPP)
            INIT(EPFNOSUPPORT)
            INIT(EAFNOSUPPORT)
            INIT(EADDRINUSE)
            INIT(EADDRNOTAVAIL)
            INIT(ENETDOWN)
            INIT(ENETUNREACH)
            INIT(ENETRESET)
            INIT(ECONNABORTED)
            INIT(ECONNRESET)
            INIT(ENOBUFS)
            INIT(EISCONN)
            INIT(ENOTCONN)
            INIT(ESHUTDOWN)
            INIT(ETOOMANYREFS)
            INIT(ETIMEDOUT)
            INIT(ECONNREFUSED)
            INIT(ELOOP)
            INIT(ENAMETOOLONG)
            INIT(EHOSTDOWN)
            INIT(EHOSTUNREACH)
            INIT(ENOTEMPTY)
            INIT(EUSERS)
            INIT(EDQUOT)
            INIT(ESTALE)
            INIT(EREMOTE)
            INIT(ENOLCK)
            INIT(ENOSYS)
            INIT(EIDRM)
            INIT(ENOMSG)
            INIT(EOVERFLOW)
            INIT(ECANCELED)
            INIT(EILSEQ)
            INIT(EBADMSG)
#ifdef EMULTIHOP
            INIT(EMULTIHOP)
#endif
            INIT(ENOLINK)
            INIT(EPROTO)
#undef INIT
        }

        void Out(IOutputStream& o, int e) const {
            if (e > 0 && e < (int)Y_ARRAY_SIZE(E_) && !E_[e].empty()) {
                ::Out<TStringBuf>(o, E_[e]);
            } else {
                ::Out<int>(o, e);
            }
        }

    private:
        TStringBuf E_[256];
    };
}

namespace NSrvKernel {
    // https://elixir.bootlin.com/linux/v4.19.85/source/net/ipv6/icmp.c#L1037
    constexpr std::initializer_list<int> CONN_REFUSED_ERRORS = {ECONNREFUSED, ENETUNREACH, EHOSTUNREACH, EACCES};

    bool ErrorIsConnRefused(int err) {
        return IsIn(CONN_REFUSED_ERRORS, err);
    }

    bool ErrorIsConnRefused(const TError& err) {
        return ErrorHasErrno(err, CONN_REFUSED_ERRORS);
    }
}

template <>
void Out<NSrvKernel::TErrno>(IOutputStream& o, const NSrvKernel::TErrno& e) {
    Default<TErrnoOutHelper>().Out(o, e.E);
}

template <>
void Out<NSrvKernel::THTTP2ConnectionError::TErrorReason>(IOutputStream& o, const NSrvKernel::THTTP2ConnectionError::TErrorReason& e) {
    std::visit([&o](auto reason) {
        if constexpr (std::is_same_v<std::decay_t<decltype(reason)>, NSrvKernel::THTTP2Error::TUnspecifiedReason>) {
            o << "UnspecifiedReason";
        } else {
            o << reason;
        }
    }, e);
}

template <>
void Out<NSrvKernel::THTTP2StreamError::TErrorReason>(IOutputStream& o, const NSrvKernel::THTTP2StreamError::TErrorReason& e) {
    std::visit([&o](auto reason) {
        if constexpr (std::is_same_v<std::decay_t<decltype(reason)>, NSrvKernel::THTTP2Error::TUnspecifiedReason>) {
            o << "UnspecifiedReason";
        } else {
            o << reason;
        }
    }, e);
}

TBackendError::TBackendError(const TBackendError& other)
    : FastError_{other.FastError_}
    , InnerError_{MakeHolder<TError>(other.InnerError_->Clone())}
    , ErrorMessage_{other.ErrorMessage_}
{
    *this << GetErrorMessage(*InnerError_);
}

TBackendError::TBackendError(TError&& error)
    : InnerError_(MakeHolder<TError>(std::move(error)))
{
    *this << GetErrorMessage(*InnerError_);
}

TBackendError::TBackendError(TError&& error, bool fastError)
    : FastError_(fastError)
    , InnerError_(MakeHolder<TError>(std::move(error)))
{
    *this << GetErrorMessage(*InnerError_);
}

void TBackendError::StoreTo(TError& error) noexcept  {
    error = std::move(*InnerError_);
}

const TError & TBackendError::InnerError() const noexcept {
    return *InnerError_;
}
