#include "ObjectAllocator.h"
#include <iostream>
#include <cstdio>
//#include <typeinfo>

struct PerClassStatistics {
    PerClassStatistics() : 
        num_operator_new_calls(0),
        num_operator_delete_calls(0),
        num_max_allocated(0) {}
    unsigned num_operator_new_calls;
    unsigned num_operator_delete_calls;
    unsigned num_max_allocated;          //max number of concurrent allocations
};

#define USE_ALLOCATOR

////////////////////////////////////////////////////////////////////////////////
#define MMS(CN,Config) \
    public: \
        static void *operator new(size_t) {  \
            if (Config::DebugOn) { \
                if ( (++stats.num_operator_new_calls-stats.num_operator_delete_calls) \
                        > stats.num_max_allocated) { \
                    stats.num_max_allocated = \
                    stats.num_operator_new_calls-stats.num_operator_delete_calls; \
                }\
            } \
            return ObjectAllocator< sizeof(CN), Config>::Allocate();  \
        } \
        static void operator delete(void *object) {  \
            if (Config::DebugOn) /* now check delete NULL*/\
                if (object) ++stats.num_operator_delete_calls; \
            ObjectAllocator< sizeof(CN), Config>::Free(object); \
        } \
        static unsigned MMAllocations() { return OAStatsPerSize<sizeof(CN)>::Allocations(); } \
        static unsigned MMDeallocations() { return OAStatsPerSize<sizeof(CN)>::Deallocations(); } \
        static unsigned MMAllocated() { return OAStatsPerSize<sizeof(CN)>::Allocated(); } \
        static unsigned MMAllocatedMax() { return OAStatsPerSize<sizeof(CN)>::MostAllocated(); } \
        static void MemManagerConfig() { \
            ObjectAllocator< sizeof(CN), Config>::PrintConfig(std::cout); \
        } \
		static unsigned Allocations()   { return stats.num_operator_new_calls; } \
		static unsigned Deallocations() { return stats.num_operator_delete_calls; } \
		static unsigned Allocated()     { return stats.num_operator_new_calls-stats.num_operator_delete_calls; } \
		static unsigned AllocatedMax()  { return stats.num_max_allocated; } \
    private: \
        static PerClassStatistics stats; //does not change the size of the class CN!!!!
////////////////////////////////////////////////////////////////////////////////
#define MMS_EXTRA(CN,Config) PerClassStatistics CN::stats;
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
#define QUOTEME(x) #x
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
#define NOMMS(CN,Config) \
    public: \
        static void MemManagerStats() { \
            std::cout << "Class " + #CN + " uses built-in memory manager\n"; \
        } \
        static void MemManagerConfig() { \
            std::cout << "Class" << #CN << " uses built-in memory manager\n"; \
        } \
        static unsigned MMAllocations() { return OAStatsPerSize<sizeof(CN)>::Allocations(); } \
        static unsigned MMDeallocations() { return OAStatsPerSize<sizeof(CN)>::Deallocations(); } \
        static unsigned MMAllocated() { return OAStatsPerSize<sizeof(CN)>::Allocated(); } \
        static unsigned MMAllocatedMax() { return OAStatsPerSize<sizeof(CN)>::MostAllocated(); } \
		static unsigned Allocations()   { return 0; } \
		static unsigned Deallocations() { return 0; } \
		static unsigned Allocated()     { return 0; } \
		static unsigned AllocatedMax()  { return 0; } \
        private:                                                               
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//Alignment PadBytes HeaderBlocks ObjectsPerPage MaxPages DebugOn UseCPPMemManager
//typedef Config<8,0,0,1000,10000,false,true> MyConfig;
typedef Config<8,0,0,20,10000,true,false> MyConfig;

////////////////////////////////////////////////////////////////////////////////
class C {
    ///////////////////////////////////////////
    //  memory manager related methods and data
#ifdef USE_ALLOCATOR
    MMS(C,MyConfig)
#else
    NOMMS(C,MyConfig)
#endif
    ///////////////////////////////////////////
    private:
        int i,j;
        double d;
    public:
        C() : i(0),j(0),d(0.0){ /*std::cout << "C()\n";*/ }
        C(C const& rhs) : i(rhs.i),j(rhs.j),d(rhs.d){ /*std::cout << "C(C const&)\n";*/ }
};
#ifdef USE_ALLOCATOR
    MMS_EXTRA(C,MyConfig)
#endif

class D {
    ///////////////////////////////////////////
    //  memory manager related methods and data
#ifdef USE_ALLOCATOR
    MMS(D,MyConfig)
#else
    NOMMS(D,MyConfig)
#endif
    ///////////////////////////////////////////
    public:
        int i,j,k,l;
        D() : i(0),j(0),k(0),l(0) { /*std::cout << "D()\n";*/ }
};
#ifdef USE_ALLOCATOR
    MMS_EXTRA(D,MyConfig)
#endif

int main1() {
    std::cout << "sizeof(C) = " << sizeof(C) << std::endl;
    std::cout << "sizeof(D) = " << sizeof(D) << std::endl;
    std::cout << "sizeof(PerClassStatistics) = " << sizeof(PerClassStatistics) << std::endl;
    C* p1 = new C;
    C* p2 = new C(*p1);
    C* p3 = new C;
    C* p4 = new C;
    C* p5 = new C;
    C c1,c2;
    delete p1;
    delete p2;
    D* p6 = new D;
    D* p7 = new D;
    std::cout << "Class C:\n"; 
    std::cout << "  Allocations   " << C::Allocations() << std::endl;
    std::cout << "  Deallocations " << C::Deallocations() << std::endl;
    std::cout << "  Allocated     " << C::Allocated() << std::endl;
    std::cout << "  AllocatedMax  " << C::AllocatedMax() << std::endl;
    std::cout << "Class D:\n"; 
    std::cout << "  Allocations   " << D::Allocations() << std::endl;
    std::cout << "  Deallocations " << D::Deallocations() << std::endl;
    std::cout << "  Allocated     " << D::Allocated() << std::endl;
    std::cout << "  AllocatedMax  " << D::AllocatedMax() << std::endl;
    delete p3;
    delete p4;
    delete p5;
    delete p6;
    delete p7;
    return 0;
}

#include <random>
#include <set>
#include <chrono>
#include <new>

template < typename T >
struct Mallocator {
    typedef T value_type;

    Mallocator() {
        std::cout << "Allocator for " << typeid(T).name() << std::endl;
    }

    //    template <typename U> constexpr Mallocator(const Mallocator<U>&) noexcept {}
    /*[[nodiscard]] c++20 */ T* allocate(std::size_t n) {
        if(n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc();
        if( auto p = static_cast<T*>(
                    //std::malloc(n*sizeof(T))
                    ObjectAllocator< sizeof(T), MyConfig>::Allocate() // only works for single object
                    )) { 
            //std::cout << "allocate " << sizeof(T) << "\n";
            return p; 
        }
        throw std::bad_alloc();
    }

    void deallocate(T* p, std::size_t) noexcept { 
        //std::cout << "de-allocate\n";
        //std::free(p); 
        ObjectAllocator< sizeof(T), MyConfig>::Free( p );
    }
};

int main() {
    int const array_size = 1000;
    C* pC[array_size];
    D* pD[array_size];

    for ( int i=0; i<array_size; ++i ) {
        pC[i] = nullptr;
        pD[i] = nullptr;
    }

    std::default_random_engine generator;
    std::uniform_int_distribution<int> rb(0,1); // 0 or 1
    std::uniform_int_distribution<int> position(0,array_size-1); // position

    for (int i=0;i<10000;++i) {
        unsigned pos   = position(generator);
        unsigned which = rb(generator);

        if (which==0) {
            delete pC[pos];
            pC[pos] = new C;
        } else {
            delete pD[pos];
            pD[pos] = new D;
        }
    }
//    D::MemManagerConfig();
    std::cout << "Size " << sizeof(C) << ":\n"; 
    std::cout << "  Allocations   " << C::MMAllocations() << std::endl;
    std::cout << "  Deallocations " << C::MMDeallocations() << std::endl;
    std::cout << "  Allocated     " << C::MMAllocated() << std::endl;
    std::cout << "  AllocatedMax  " << C::MMAllocatedMax() << std::endl;
    std::cout << "Size " << sizeof(D) << ":\n"; 
    std::cout << "  Allocations   " << D::MMAllocations() << std::endl;
    std::cout << "  Deallocations " << D::MMDeallocations() << std::endl;
    std::cout << "  Allocated     " << D::MMAllocated() << std::endl;
    std::cout << "  AllocatedMax  " << D::MMAllocatedMax() << std::endl;
    std::cout << "Class C:\n"; 
    std::cout << "  Allocations   " << C::Allocations() << std::endl;
    std::cout << "  Deallocations " << C::Deallocations() << std::endl;
    std::cout << "  Allocated     " << C::Allocated() << std::endl;
    std::cout << "  AllocatedMax  " << C::AllocatedMax() << std::endl;
    std::cout << "Class D:\n"; 
    std::cout << "  Allocations   " << D::Allocations() << std::endl;
    std::cout << "  Deallocations " << D::Deallocations() << std::endl;
    std::cout << "  Allocated     " << D::Allocated() << std::endl;
    std::cout << "  AllocatedMax  " << D::AllocatedMax() << std::endl;



    std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
    using MySet = std::set<int, std::less<int>, Mallocator<int>>;
    MySet s;

    int num_iter = 10000000;
    int range = 1000;
    std::uniform_int_distribution<int> value(0,range);
    for ( int i=0;i<num_iter;++i ) {
        int val = value(generator);
        unsigned which = rb(generator);

        if (which==0) {
            MySet::const_iterator it = s.find(val);
            if ( it != s.end() ) {
                s.erase( it );
            }
        } else {
            s.insert( val );
        }
    }

    std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end-start;
    std::cout << "time elapsed " << elapsed_seconds.count() << std::endl;

    std::cout << "Size " << 40 << ":\n"; 
    std::cout << "  Allocations   " << OAStatsPerSize<40>::Allocations() << std::endl;
    std::cout << "  Deallocations " << OAStatsPerSize<40>::Deallocations() << std::endl;
    std::cout << "  Allocated     " << OAStatsPerSize<40>::Allocated() << std::endl;


    start = std::chrono::system_clock::now();
    using MySet2 = std::set<int, std::less<int>>;
    MySet2 s2;

    for ( int i=0;i<num_iter;++i ) {
        int val = value(generator);
        unsigned which = rb(generator);

        if (which==0) {
            MySet2::const_iterator it = s2.find(val);
            if ( it != s2.end() ) {
                s2.erase( it );
            }
        } else {
            s2.insert( val );
        }
    }

    end = std::chrono::system_clock::now();
    elapsed_seconds = end-start;
    std::cout << "time elapsed " << elapsed_seconds.count() << std::endl;
}
// do debug/statistics
//Size 16:
//  Allocations   0
//  Deallocations 0
//  Allocated     0
//  AllocatedMax  0
//Size 16:
//  Allocations   0
//  Deallocations 0
//  Allocated     0
//  AllocatedMax  0
//Class C:
//  Allocations   0
//  Deallocations 0
//  Allocated     0
//  AllocatedMax  0
//Class D:
//  Allocations   0
//  Deallocations 0
//  Allocated     0
//  AllocatedMax  0
//Allocator for St13_Rb_tree_nodeIiE
//time elapsed 1.09962
//Size 40:
//  Allocations   0
//  Deallocations 0
//  Allocated     0
//time elapsed 1.21435



//
// with statistics
//Size 16:
//  Allocations   10000
//  Deallocations 8014
//  Allocated     1986
//  AllocatedMax  1986
//Size 16:
//  Allocations   10000
//  Deallocations 8014
//  Allocated     1986
//  AllocatedMax  1986
//Class C:
//  Allocations   4981
//  Deallocations 3987
//  Allocated     994
//  AllocatedMax  994
//Class D:
//  Allocations   5019
//  Deallocations 4027
//  Allocated     992
//  AllocatedMax  992
//Allocator for St13_Rb_tree_nodeIiE
//time elapsed 1.51597
//Size 40:
//  Allocations   2500946
//  Deallocations 2500431
//  Allocated     515
//time elapsed 1.22748