#ifndef __UT_EXCEPTION_HPP__
#define __UT_EXCEPTION_HPP__

#include <unitree/common/error.hpp>

#define UT_MAX_TRACE_ADDR_NUMBER 64

namespace unitree
{
namespace common
{
class Exception : public std::exception
{
public:
    Exception() throw()
        : Exception(UT_ERR_UNKNOWN, UT_DESC_ERR(UT_ERR_UNKNOWN))
    {}

    Exception(int32_t code, const std::string& message) throw()
        : mCode(code), mMessage(message), mLine(0)
    {}

    virtual ~Exception()
    {}

    int32_t GetCode() const
    {
        return mCode;
    }

    const std::string& GetMessage() const
    {
        return mMessage;
    }

    virtual const char* what() const noexcept
    {
        return mMessage.c_str();
    }

    virtual std::string GetClassName() const
    {
        return "Exception";
    }

    void Init(const char* file, const char* func, int32_t line)
    {
        mFile = file;
        mFunc = func;
        mLine = line;
    }

    std::string ToString() const
    {
        std::ostringstream os;
        AddDetail(os);

        return os.str();
    }

    std::string StackTrace() const
    {
        std::ostringstream os;
        AddDetail(os);
        AddBackTrace(os);

        return os.str();
    }

protected:
    void AddDetail(std::ostringstream& os) const
    {
        os << "Catch " << GetClassName() << " code:" << mCode
            << ", message:" << mMessage << std::endl;

        if (!mFile.empty() && !mFunc.empty() && mLine > 0)
        {
            os << " at __FILE__:" << mFile << ", __LINE__:"
                << mLine << ", __FUNCTION__:" << mFunc << std::endl;
        }
    }

    void AddBackTrace(std::ostringstream& os) const
    {
        os << "Stack:" << std::endl;

        void* addr[UT_MAX_TRACE_ADDR_NUMBER];
        int32_t number = backtrace(addr, UT_MAX_TRACE_ADDR_NUMBER);

        char **info = backtrace_symbols(addr, number);
        if(info == NULL)
        {
            return;
        }

        for(int32_t i=0; i<number; i++)
        {
            os << info[i] << std::endl;
        }

        free(info);
    }

protected:
    int32_t mCode;
    std::string mMessage;

    std::string mFile;
    std::string mFunc;
    int32_t mLine;

    std::string mWhat;
};

#define UT_THROW_0(ExceptionType)           \
    do                                      \
    {                                       \
        ExceptionType __temp_except_r38e2d; \
        __temp_except_r38e2d.Init(__FILE__,__PRETTY_FUNCTION__,__LINE__); \
        throw(__temp_except_r38e2d);        \
    } while(0);

#define UT_THROW(ExceptionType, args...)    \
    do                                      \
    {                                       \
        ExceptionType __temp_except_r38e2d(args);       \
        __temp_except_r38e2d.Init(__FILE__,__PRETTY_FUNCTION__,__LINE__); \
        throw(__temp_except_r38e2d);        \
    } while(0);

#define UT_THROW_IF(condition, ExceptionType, args...)  \
    if (condition)                          \
    {                                       \
        UT_THROW(ExceptionType, args);      \
    }

#define UT_EXCEPTION_TRY                    \
    try                                     \
    {

#define __UT_EXCEPTION_CATCH(except, l, t)  \
    catch (const except& e)                 \
    {                                       \
        if (l)                              \
        {                                   \
            LOG_ERROR(l, e.what());         \
        }                                   \
        if (t)                              \
        {                                   \
            throw e;                        \
        }                                   \
    }

#define UT_EXCEPTION_CATCH(l, t)            \
    }                                       \
    __UT_EXCEPTION_CATCH(unitree::common::Exception, l, t)      \
    __UT_EXCEPTION_CATCH(std::exception, l, t)

#define UT_DECL_EXCEPTION(ExceptionType, code, desc)    \
class ExceptionType : public Exception      \
{                                           \
public:                                     \
    ExceptionType() throw()                 \
        : Exception(code, desc)             \
    {}                                      \
                                            \
    ExceptionType(const std::string& message) throw()   \
        : Exception(code, message)          \
    {}                                      \
                                            \
    std::string GetClassName() const        \
    {                                       \
        return #ExceptionType;              \
    }                                       \
};

UT_DECL_EXCEPTION(CommonException, UT_ERR_COMMON, UT_DESC_ERR(UT_ERR_COMMON))

UT_DECL_EXCEPTION(SystemException, UT_ERR_SYSTEM, UT_DESC_ERR(UT_ERR_SYSTEM))

UT_DECL_EXCEPTION(NetworkException, UT_ERR_NETWORK, UT_DESC_ERR(UT_ERR_NETWORK))

UT_DECL_EXCEPTION(FileException, UT_ERR_FILE, UT_DESC_ERR(UT_ERR_FILE))

UT_DECL_EXCEPTION(SocketException, UT_ERR_SOCKET, UT_DESC_ERR(UT_ERR_SOCKET))

UT_DECL_EXCEPTION(IOException, UT_ERR_IO, UT_DESC_ERR(UT_ERR_IO))

UT_DECL_EXCEPTION(LockException, UT_ERR_LOCK, UT_DESC_ERR(UT_ERR_LOCK))

UT_DECL_EXCEPTION(TimeoutException, UT_ERR_TIMEOUT, UT_DESC_ERR(UT_ERR_TIMEOUT))

UT_DECL_EXCEPTION(BadCastException, UT_ERR_BADCAST, UT_DESC_ERR(UT_ERR_BADCAST))

UT_DECL_EXCEPTION(JsonException, UT_ERR_JSON, UT_DESC_ERR(UT_ERR_JSON))

UT_DECL_EXCEPTION(FutureException, UT_ERR_FUTURE, UT_DESC_ERR(UT_ERR_FUTURE))

UT_DECL_EXCEPTION(FutureFaultException, UT_ERR_FUTURE_FAULT, UT_DESC_ERR(UT_ERR_FUTURE_FAULT))

}
}
#endif//__UT_EXCEPTION_HPP__
