#pragma once

#include <unordered_map>
#include <system_error>

#include "aqsi-commons-global.h"

namespace aqsi {

/*!
 * \def
 * This macro provides a user the preferred way to create AqsiError objects
 */
#define MakeAqsiError(ec, DESCRIPTION) aqsi::AqsiError(__FILE__, __LINE__, __func__, ec, DESCRIPTION)
#define MakeAqsiErrorFromErrno(errno_value)                                                     \
    aqsi::AqsiError(                                                                            \
        __FILE__, __LINE__, __func__, aqsi::AqsiException::errorCodeFromLibcErrno(errno_value), \
        std::strerror(errno_value))

#define MakeAqsiErrorFromErrnoWithDescription(errno_value, DESCRIPTION)                         \
    aqsi::AqsiError(                                                                            \
        __FILE__, __LINE__, __func__, aqsi::AqsiException::errorCodeFromLibcErrno(errno_value), \
        DESCRIPTION + std::string{" Код ошибки: "} + std::to_string(errno_value) +              \
            ". Описание ошибки: " + std::string(std::strerror(errno_value)))

/*! \brief AqsiError class holds the information about error occured
 *
 *  \note The preferred way of creating AqsiError object is using
 *  'MakeAqsiError' macro:
 *  \code
        MakeAqsiError(SomeErrorType::kParticularError, "detailed error description")
 *  \endcode
 *  If using 'MakeAqsiError' macro, the information about source file, source
 *  line number, and function where the error has occured is stored in AqsiError
 *  object.
 */
class AQSICOMMONS_EXPORT AqsiError : public std::error_code
{
public:
    AqsiError()
    {}

    template <class ErrorCodeEnum>
    /*! \brief Constructor with no info about error source
     *
     * This constructor is not recommended to invoke directly by user. It is suggested
     * for user to use MakeAqsiError() macro. @see MakeAqsiError()
     *
     * \param ec Error code
     * \param desc Detailed error description. If no detailed description is provided by
     * user, the default description is used.
     */
    AqsiError(ErrorCodeEnum ec, std::string const& desc = std::string())
        : std::error_code{ec}, m_description{desc.size() ? desc : message()}
    {}

    template <class ErrorCodeEnum>
    /*! \brief Constructor with info about error source
     *
     * This constructor is not recommended to invoke directly by user. It is suggested
     * for user to use MakeAqsiError() macro. @see MakeAqsiError()
     *
     * \param file Source code file name where the error occured
     * \param line Source code line number where the error occured
     * \param func Source code function name where the error occured
     * \param desc Detailed error description. If no detailed description is provided by
     * user, the default description is used.
     * \param ec Error code
     */
    AqsiError(char const* file, int line, char const* func, ErrorCodeEnum ec, std::string const& desc = std::string())
        : std::error_code{ec},
          m_file_errsrc{file},
          m_line_errsrc{line},
          m_func_errsrc{func},
          m_description{desc.size() ? desc : message()}
    {}

    inline char const*
    file() const
    {
        return m_file_errsrc;
    }

    inline int
    line() const
    {
        return m_line_errsrc;
    }

    inline char const*
    func() const
    {
        return m_func_errsrc;
    }

    inline char const*
    description() const
    {
        return m_description.c_str();
    }

private:
    char const* m_file_errsrc = nullptr;
    int m_line_errsrc         = 0;
    char const* m_func_errsrc = nullptr;
    std::string m_description;
};

template <typename T>
AQSICOMMONS_EXPORT std::string_view
findErrorStandardMsg(T ev, std::unordered_map<T, std::string_view> const& err_map)
{
    auto pos = err_map.find(static_cast<T>(ev));
    if (pos != err_map.end()) {
        return pos->second;
    } else {
        return "(unrecognized error)";
    }
}
}  // namespace aqsi
