#pragma once

#include <stdexcept>
#include <unordered_map>

#include "AqsiError.h"
#include "aqsi_error_code.h"

namespace aqsi {
/*! \brief AqsiException class stores std::error_code class or derived from it
 *  AqsiError class instance
 *
 *  \note Usage example:
 *  \code
     try {
         throw AqsiException(MakeAqsiError(
             SomeErrorType::kParticularError, "detailed error description");
     }catch(std::exception& e) {
         std::cout << "catched exception " << e.what() << std::endl;
     }
 *  \endcode
 */
class AQSICOMMONS_EXPORT AqsiException : public std::exception
{
public:
    AqsiException() noexcept
    {}

    AqsiException(std::error_code const& ec) noexcept : m_ec(ec)
    {}

    AqsiException(AqsiError const& ec) noexcept : m_ec(ec)
    {}

    virtual ~AqsiException() = default;

    virtual char const*
    what() const noexcept override
    {
        char const* desc = m_ec.description();
        char const* file = m_ec.file();
        int const line   = m_ec.line();
        char const* func = m_ec.func();

        m_msg = m_ec.category().name() + std::string(":") + std::to_string(m_ec.value()) + ", " + std::string(desc) +
                (file ? ", file: " + std::string(file) : "") + (line ? ", line: " + std::to_string(line) : "") +
                (func ? ", func: " + std::string(func) + "()" : "");

        return m_msg.c_str();
    }

    std::error_code const&
    getErrorCode() const
    {
        return m_ec;
    }

    static AqsiErrorCode
    errorCodeFromLibcErrno(int errno_value)
    {
        static const std::unordered_map<int, AqsiErrorCode> kErrnoToErrorCode{
            {EPERM, AqsiErrorCode::kPermissionsError},       {ENOENT, AqsiErrorCode::kNoSuchFileOrDirectoryError},
            {EBADF, AqsiErrorCode::kFileOpenError},          {EINTR, AqsiErrorCode::kAbortError},
            {EINVAL, AqsiErrorCode::kInvalidArgumentsError}, {ENOLCK, AqsiErrorCode::kResourceError},
            {EACCES, AqsiErrorCode::kPermissionsError},      {EBUSY, AqsiErrorCode::kDeviceBlockedError},
            {EDQUOT, AqsiErrorCode::kResourceError},         {EEXIST, AqsiErrorCode::kFileCreateError},
            {EFAULT, AqsiErrorCode::kPermissionsError},      {EFBIG, AqsiErrorCode::kResourceError},
            {EISDIR, AqsiErrorCode::kUnsupported},           {ELOOP, AqsiErrorCode::kFileOpenError},
            {EMFILE, AqsiErrorCode::kResourceError},         {ENAMETOOLONG, AqsiErrorCode::kFileOpenError},
            {ENFILE, AqsiErrorCode::kResourceError},         {ENODEV, AqsiErrorCode::kFileOpenError},
            {ENOENT, AqsiErrorCode::kFileCreateError},       {ENOMEM, AqsiErrorCode::kResourceError},
            {ENOSPC, AqsiErrorCode::kResourceError},         {ENOTDIR, AqsiErrorCode::kInvalidArgumentsError},
            {ENXIO, AqsiErrorCode::kFileOpenError},          {EOPNOTSUPP, AqsiErrorCode::kUnsupported},
            {EOVERFLOW, AqsiErrorCode::kFileOpenError},      {EROFS, AqsiErrorCode::kFileOpenError},
            {ETXTBSY, AqsiErrorCode::kFileWriteError},       {EWOULDBLOCK, AqsiErrorCode::kResourceLockedError},
            {EAGAIN, AqsiErrorCode::kResourceLockedError},   {EDESTADDRREQ, AqsiErrorCode::kExecutionError},
            {EIO, AqsiErrorCode::kFileWriteError},           {ENOSPC, AqsiErrorCode::kResourceError},
            {EPIPE, AqsiErrorCode::kPermissionsError}};

        auto const iter = kErrnoToErrorCode.find(errno_value);

        return iter == kErrnoToErrorCode.end() ? AqsiErrorCode::kUnknown : iter->second;
    }

private:
    AqsiError m_ec;
    mutable std::string m_msg;
};
}  // namespace aqsi
