#pragma once
#include "lite_build_config.h"

#include <chrono>
#include <exception>
#include <stdexcept>
#include <string>
#include "lite/common_enum_c.h"
#include "lite/global.h"

namespace lite {
#if LITE_ENABLE_EXCEPTION
/*! \brief The error class in lite.
 *
 * It can be used to represent both an error caused by the invalid
 * input of the caller or an invalid runtime condition.
 *
 * The necessary presumption should be guaranteed by assertions instead of
 * exceptions.
 */
class Error : public std::exception {
public:
    Error(const std::string& msg) : m_msg("Error: " + msg) {}
    const char* what() const noexcept override { return m_msg.c_str(); }

private:
    std::string m_msg;
};
#endif

LITE_API std::string ssprintf(const char* fmt = 0, ...)
        __attribute__((format(printf, 1, 2)));

/*!
 * \brief Print a message.
 *
 * The message is printed only if level is above or equals to the current log
 * level.
 */
LITE_API void print_log(LiteLogLevel level, const char* format = 0, ...)
        __attribute__((format(printf, 2, 3)));

/*!
 * \brief NonCopyableObj base.
 */
class NonCopyableObj {
public:
    NonCopyableObj() {}

private:
    NonCopyableObj(const NonCopyableObj&);
    NonCopyableObj& operator=(const NonCopyableObj&);
};

template <class T>
class Singleton : public NonCopyableObj {
public:
    Singleton() {}
    static T& Instance() {
        static T _;
        return _;
    }
};

}  // namespace lite

#if LITE_ENABLE_LOGGING
#define LITE_LOG_(level, msg...)                     \
    do {                                             \
        lite::print_log(LiteLogLevel::level, ##msg); \
    } while (0)
#else
#define LITE_LOG_(level, msg...) (void)0
#endif

#define LITE_LOG(fmt...)   LITE_LOG_(INFO, fmt);
#define LITE_DEBUG(fmt...) LITE_LOG_(DEBUG, fmt);
#define LITE_WARN(fmt...)  LITE_LOG_(WARN, fmt);
#define LITE_ERROR(fmt...) LITE_LOG_(ERROR, fmt);

#if LITE_ENABLE_EXCEPTION
#define LITE_THROW(msg) throw lite::Error(msg)
#else
#define LITE_THROW(msg)              \
    do {                             \
        std::string msg_str(msg);    \
        LITE_ERROR(msg_str.c_str()); \
        __builtin_trap();            \
    } while (0)
#endif

#if LITE_ENABLE_EXCEPTION
#define LITE_ERROR_HANDLER_BEGIN try {
#define LITE_ERROR_HANDLER_END                                        \
    }                                                                 \
    catch (const ::lite::Error& e) {                                  \
        std::string msg = std::string("Lite exception: ") + e.what(); \
        LITE_ERROR("%s.", msg.c_str());                               \
        throw;                                                        \
    }

#else
#define LITE_ERROR_HANDLER_BEGIN
#define LITE_ERROR_HANDLER_END
#endif

/*! \brief Return an error if the given pointer is null pointer.
 *
 * The macro is used to ensure the validity of the passing context pointer.
 */
#define LITE_CHECK_NON_NULL_POINTER(ptr) \
    LITE_ASSERT(ptr != nullptr, "Input ptr is null.")

//! branch prediction hint: likely to take
#define lite_likely(v) __builtin_expect(static_cast<bool>(v), 1)

//! branch prediction hint: unlikely to take
#define lite_unlikely(v) __builtin_expect(static_cast<bool>(v), 0)

#if LITE_ENABLE_LOGGING
#if LITE_ASSERT_LOC
#define LITE_ASSERT(expr, msg...)                                                   \
    do {                                                                            \
        if (lite_unlikely(!(expr))) {                                               \
            auto info = lite::ssprintf(msg);                                        \
            LITE_THROW(lite::ssprintf(                                              \
                    "Assert \' %s \' failed at file : %s \n"                        \
                    "line %d : %s,\nextra "                                         \
                    "message: %s",                                                  \
                    #expr, __FILE__, __LINE__, __PRETTY_FUNCTION__, info.c_str())); \
        }                                                                           \
    } while (0)
#else
#define LITE_ASSERT(expr, msg...)                                                    \
    do {                                                                             \
        if (lite_unlikely(!(expr))) {                                                \
            auto info = lite::ssprintf(msg);                                         \
            LITE_THROW(lite::ssprintf(                                               \
                    "Assert \' %s \' failed at file : %s \n"                         \
                    "line %d : %s,\nextra "                                          \
                    "message: %s",                                                   \
                    #expr, "about location info, please build with debug", __LINE__, \
                    __PRETTY_FUNCTION__, info.c_str()));                             \
        }                                                                            \
    } while (0)
#endif
#else
#define LITE_ASSERT(expr, msg...)                  \
    do {                                           \
        if (lite_unlikely(!(expr))) {              \
            auto msg_string = lite::ssprintf(msg); \
            LITE_THROW(msg_string.c_str());        \
        }                                          \
    } while (0)
#endif

#define LITE_MARK_USED_VAR(var) ((void)var)

namespace lite {
class ScopedTimer {
public:
    typedef std::chrono::system_clock Clock;
    typedef std::chrono::nanoseconds Nsec;

    ScopedTimer(std::string name) : m_name(name) { m_start = Clock::now(); }
    ~ScopedTimer() {
        m_stop = Clock::now();
        std::chrono::duration<double> elapsed = m_stop - m_start;
        Nsec u = std::chrono::duration_cast<Nsec>(elapsed);
        auto msg = ssprintf(
                "%s used time %fms.", m_name.c_str(),
                static_cast<double>(u.count()) / 1000000.f);
        LITE_LOG("%s", msg.c_str());
    }

private:
    std::chrono::time_point<std::chrono::system_clock> m_start, m_stop;
    const std::string m_name;
};

class Timer {
public:
    typedef std::chrono::system_clock Clock;
    typedef std::chrono::nanoseconds Nsec;

    Timer(std::string name) : m_name(name) { m_start = Clock::now(); }
    double get_used_time() {
        m_stop = Clock::now();
        std::chrono::duration<double> elapsed = m_stop - m_start;
        Nsec u = std::chrono::duration_cast<Nsec>(elapsed);
        return static_cast<double>(u.count()) / 1000000.0;
    }
    void print_used_time(int iter) {
        m_stop = Clock::now();
        std::chrono::duration<double> elapsed = m_stop - m_start;
        Nsec u = std::chrono::duration_cast<Nsec>(elapsed);
        printf("%s used time %f ms\n", (m_name + std::to_string(iter)).c_str(),
               static_cast<double>(u.count()) / 1000000.0);
    }
    void reset_start() { m_start = Clock::now(); }

private:
    std::chrono::time_point<std::chrono::system_clock> m_start, m_stop;
    const std::string m_name;
};

inline void mark_used_variable() {}
template <typename T, typename... Arg>
inline void mark_used_variable(T firstArg, Arg... args) {
    LITE_MARK_USED_VAR(firstArg);
    mark_used_variable(args...);
}
}  // namespace lite

#if defined(_WIN32)
#include <io.h>
#include <windows.h>
#undef CONST
#define F_OK      0
#define RTLD_LAZY 0
// On the windows platform we use a lib_filename without a full path so
// the win-api "LoadLibrary" would uses a standard search strategy to
// find the lib module. As we cannot access to the lib_filename without a
// full path, we should not use "access(a, b)" to verify it.
#define access(a, b) false
static inline void* dlopen(const char* file, int) {
    return static_cast<void*>(LoadLibrary(file));
}

static inline char* dlerror() {
    const char* errmsg = "dlerror not aviable in windows";
    return const_cast<char*>(errmsg);
}

static inline void* dlsym(void* handle, const char* name) {
    FARPROC symbol = GetProcAddress((HMODULE)handle, name);
    return reinterpret_cast<void*>(symbol);
}
#elif __linux__ || __unix__ || __APPLE__
#include <dlfcn.h>
#include <unistd.h>
#endif

#if __DEPLOY_ON_XP_SP2__
//! refer to
//! https://docs.microsoft.com/en-us/cpp/build/configuring-programs-for-windows-xp?view=msvc-160
//! xp sp2 do not support vc runtime fully, casused by KERNEL32.dll do not
//! implement some base apis for c++ std function, for example,
//! std::mutex/std::thread/std::condition_variable as a workround, we will
//! disable some MegEngine feature on xp sp2 env, for exampe, multi-thread etc!
#define LITE_MUTEX                  size_t
#define LITE_RECURSIVE_MUTEX        size_t
#define LITE_LOCK_GUARD(mtx)        LITE_MARK_USED_VAR(mtx)
#define LITE_LOCK_GUARD_UNIQUE(mtx) LITE_MARK_USED_VAR(mtx)
#define LITE_LOCK_GUARD_SHARED(mtx) LITE_MARK_USED_VAR(LITE_MARK_USED_VAR)
#else
#define LITE_MUTEX           std::mutex
#define LITE_RECURSIVE_MUTEX std::recursive_mutex
#define LITE_LOCK_GUARD(mtx) std::lock_guard<decltype(mtx)> LITE_LOCK_GUARD_CTOR(mtx)

#define LITE_LOCK_GUARD_UNIQUE(mtx) \
    std::unique_lock<decltype(mtx)> LITE_LOCK_GUARD_CTOR(mtx)

#define LITE_LOCK_GUARD_SHARED(mtx) \
    std::shared_lock<decltype(mtx)> LITE_LOCK_GUARD_CTOR(mtx)
#endif

// vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}
