#ifndef __MEMORYALLOCATIONSTACKTRACER_H__
#define __MEMORYALLOCATIONSTACKTRACER_H__

#include <boost/stacktrace/stacktrace.hpp>
#include <chrono>
#include <thread>
#include <unordered_map>

extern "C" {
extern void *__real_malloc(size_t size);
extern void __real_free(void *__ptr);
}

template <class T>
class DebugAllocator {
public:
    using value_type = T;
    using pointer = T *;

    typedef const T *const_pointer;
    typedef T &reference;
    typedef const T &const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    template <class U>
    struct rebind {
        typedef DebugAllocator<U> other;
    };
    DebugAllocator() {
    }

    template <class U>
    DebugAllocator(const DebugAllocator<U> &other) noexcept {
    }

    T *allocate(std::size_t n) {
        return reinterpret_cast<T *>(__real_malloc(n * sizeof(T)));
    }
    void deallocate(T *p, std::size_t n) {
        __real_free(p);
    }

    template <class U, class... Args>
    void construct(U *p, Args &&...args) {
        ::new ((void *)p) U(std::forward<Args>(args)...);
    }

    template <class U>
    void destroy(U *p) {
        p->~U();
    }

    pointer address(reference x) {
        return (pointer)&x;
    }

    const_pointer address(const_reference x) {
        return (const_pointer)&x;
    }
};

class MemoryAllocationStackTracer {
public:
    using Frame = boost::stacktrace::basic_stacktrace<DebugAllocator<boost::stacktrace::frame>>;
    using String = std::basic_string<char, std::char_traits<char>, DebugAllocator<char>>;
    using OutStringStream = std::basic_ostringstream<char, std::char_traits<char>, DebugAllocator<char>>;
    class Infomation {
    public:
        std::chrono::system_clock::time_point time;
        Frame frame;
    };
    ~MemoryAllocationStackTracer();
    static MemoryAllocationStackTracer *instance();
    inline bool enabled() const {
        return m_enabled;
    }

    template <class Rep, class Period>
    void start(const std::string &path, const std::string &applicationName,
               const std::chrono::duration<Rep, Period> &intervals) {
        if (m_exit) {
            m_exit = false;
            m_thread = std::thread(&MemoryAllocationStackTracer::run, this);
        }
        m_intervals = std::chrono::duration_cast<std::chrono::milliseconds>(intervals);

        if (!m_enabled) {
            m_enabled = true;
        }
        m_path = path;
        m_applicationName = applicationName;
    }

    void stop();

    std::string dump();
    void push(uint64_t address, Frame &&stacktrace);
    void pop(uint64_t address);

protected:
    void run();

private:
    std::unordered_map<uint64_t, Infomation, std::hash<uint64_t>, std::equal_to<uint64_t>,
                       DebugAllocator<std::pair<const uint64_t, Infomation>>>
        m_stacktraces;

    std::unordered_map<boost::stacktrace::frame::native_frame_ptr_t, uint32_t,
                       std::hash<boost::stacktrace::frame::native_frame_ptr_t>,
                       std::equal_to<boost::stacktrace::frame::native_frame_ptr_t>,
                       DebugAllocator<std::pair<const boost::stacktrace::frame::native_frame_ptr_t, uint32_t>>>
        m_counts;

    static MemoryAllocationStackTracer *m_instance;
    std::string m_path;
    std::string m_applicationName;
    bool m_exit = true;
    std::thread m_thread;
    std::chrono::milliseconds m_intervals;

    bool m_enabled = false;
};

#endif // __MEMORYALLOCATIONSTACKTRACER_H__