// Copyright (c) 2014 Baidu, Inc.
// Author: Ge,Jun (gejun@baidu.com)
// Date: Sun Jul 13 15:04:18 CST 2014

#include <gtest/gtest.h>
#include "butil/time.h"
#include "butil/macros.h"

#define BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT
#include "butil/object_pool.h"

namespace {
struct MyObject {};

int nfoo_dtor = 0;
struct Foo {
    Foo() {
        x = rand() % 2;
    }
    ~Foo() {
        ++nfoo_dtor;
    }
    int x;
};
}

namespace butil {
template <> struct ObjectPoolBlockMaxSize<MyObject> {
    static const size_t value = 128;
};

template <> struct ObjectPoolBlockMaxItem<MyObject> {
    static const size_t value = 3;
};

template <> struct ObjectPoolFreeChunkMaxItem<MyObject> {
    static size_t value() { return 5; }
};

template <> struct ObjectPoolValidator<Foo> {
    static bool validate(const Foo* foo) {
        return foo->x != 0;
    }
};
}

namespace {
using namespace butil;

class ObjectPoolTest : public ::testing::Test{
protected:
    ObjectPoolTest(){
    };
    virtual ~ObjectPoolTest(){};
    virtual void SetUp() {
        srand(time(0));
    };
    virtual void TearDown() {
    };
};

int nc = 0;
int nd = 0;
std::set<void*> ptr_set;
struct YellObj {
    YellObj() {
        ++nc;
        ptr_set.insert(this);
        printf("Created %p\n", this);
    }
    ~YellObj() {
        ++nd;
        ptr_set.erase(this);
        printf("Destroyed %p\n", this);
    }
    char _dummy[96];
};

TEST_F(ObjectPoolTest, change_config) {
    int a[2];
    printf("%lu\n", ARRAY_SIZE(a));
    
    ObjectPoolInfo info = describe_objects<MyObject>();
    ObjectPoolInfo zero_info = { 0, 0, 0, 0, 3, 3, 0 };
    ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info)));

    MyObject* p = get_object<MyObject>();
    std::cout << describe_objects<MyObject>() << std::endl;
    return_object(p);
    std::cout << describe_objects<MyObject>() << std::endl;
}

struct NonDefaultCtorObject {
    explicit NonDefaultCtorObject(int value) : _value(value) {}
    NonDefaultCtorObject(int value, int dummy) : _value(value + dummy) {}

    int _value;
};

TEST_F(ObjectPoolTest, sanity) {
    ptr_set.clear();
    NonDefaultCtorObject* p1 = get_object<NonDefaultCtorObject>(10);
    ASSERT_EQ(10, p1->_value);
    NonDefaultCtorObject* p2 = get_object<NonDefaultCtorObject>(100, 30);
    ASSERT_EQ(130, p2->_value);
    
    printf("BLOCK_NITEM=%lu\n", ObjectPool<YellObj>::BLOCK_NITEM);
    
    nc = 0;
    nd = 0;
     {
        YellObj* o1 = get_object<YellObj>();
        ASSERT_TRUE(o1);
        
        ASSERT_EQ(1, nc);
        ASSERT_EQ(0, nd);

        YellObj* o2 = get_object<YellObj>();
        ASSERT_TRUE(o2);

        ASSERT_EQ(2, nc);
        ASSERT_EQ(0, nd);
        
        return_object(o1);
        ASSERT_EQ(2, nc);
        ASSERT_EQ(0, nd);

        return_object(o2);
        ASSERT_EQ(2, nc);
        ASSERT_EQ(0, nd);
    }
    ASSERT_EQ(0, nd);

    clear_objects<YellObj>();
    ASSERT_EQ(2, nd);
    ASSERT_TRUE(ptr_set.empty()) << ptr_set.size();
}

TEST_F(ObjectPoolTest, validator) {
    nfoo_dtor = 0;
    int nfoo = 0;
    for (int i = 0; i < 100; ++i) {
        Foo* foo = get_object<Foo>();
        if (foo) {
            ASSERT_EQ(1, foo->x);
            ++nfoo;
        }
    }
    ASSERT_EQ(nfoo + nfoo_dtor, 100);
    ASSERT_EQ((size_t)nfoo, describe_objects<Foo>().item_num);
}

TEST_F(ObjectPoolTest, get_int) {
    clear_objects<int>();
    
    // Perf of this test is affected by previous case.
    const size_t N = 100000;
    
    butil::Timer tm;

    // warm up
    int* p = get_object<int>();
    *p = 0;
    return_object(p);
    delete (new int);
    
    tm.start();
    for (size_t i = 0; i < N; ++i) {
        *get_object<int>() = i;
    }
    tm.stop();
    printf("get a int takes %.1fns\n", tm.n_elapsed()/(double)N);
    
    tm.start();
    for (size_t i = 0; i < N; ++i) {
        *(new int) = i;
    }    
    tm.stop();
    printf("new a int takes %luns\n", tm.n_elapsed()/N);

    std::cout << describe_objects<int>() << std::endl;
    clear_objects<int>();
    std::cout << describe_objects<int>() << std::endl;
}


struct SilentObj {
    char buf[sizeof(YellObj)];
};

TEST_F(ObjectPoolTest, get_perf) {
    const size_t N = 10000;
    std::vector<SilentObj*> new_list;
    new_list.reserve(N);
    
    butil::Timer tm1, tm2;

    // warm up
    return_object(get_object<SilentObj>());
    delete (new SilentObj);

    // Run twice, the second time will be must faster.
    for (size_t j = 0; j < 2; ++j) {

        tm1.start();
        for (size_t i = 0; i < N; ++i) {
            get_object<SilentObj>();
        }
        tm1.stop();
        printf("get a SilentObj takes %luns\n", tm1.n_elapsed()/N);
        //clear_objects<SilentObj>(); // free all blocks
        
        tm2.start();
        for (size_t i = 0; i < N; ++i) {
            new_list.push_back(new SilentObj);
        }    
        tm2.stop();
        printf("new a SilentObj takes %luns\n", tm2.n_elapsed()/N);
        for (size_t i = 0; i < new_list.size(); ++i) {
            delete new_list[i];
        }
        new_list.clear();
    }

    std::cout << describe_objects<SilentObj>() << std::endl;
}

struct D { int val[1]; };

void* get_and_return_int(void*) {
    // Perf of this test is affected by previous case.
    const size_t N = 100000;
    std::vector<D*> v;
    v.reserve(N);
    butil::Timer tm0, tm1, tm2;
    D tmp = D();
    int sr = 0;

    // warm up
    tm0.start();
    return_object(get_object<D>());
    tm0.stop();

    printf("[%lu] warmup=%lu\n", pthread_self(), tm0.n_elapsed());

    for (int j = 0; j < 5; ++j) {
        v.clear();
        sr = 0;
        
        tm1.start();
        for (size_t i = 0; i < N; ++i) {
            D* p = get_object<D>();
            *p = tmp;
            v.push_back(p);
        }
        tm1.stop();

        std::random_shuffle(v.begin(), v.end());

        tm2.start();
        for (size_t i = 0; i < v.size(); ++i) {
            sr += return_object(v[i]);
        }
        tm2.stop();

        if (0 != sr) {
            printf("%d return_object failed\n", sr);
        }
        
        printf("[%lu:%d] get<D>=%.1f return<D>=%.1f\n",
                 pthread_self(), j, tm1.n_elapsed()/(double)N,
                 tm2.n_elapsed()/(double)N);
    }
    return NULL;
}

void* new_and_delete_int(void*) {
    const size_t N = 100000;
    std::vector<D*> v2;
    v2.reserve(N);
    butil::Timer tm0, tm1, tm2;
    D tmp = D();

    for (int j = 0; j < 3; ++j) {
        v2.clear();
        
        // warm up
        delete (new D);

        tm1.start();
        for (size_t i = 0; i < N; ++i) {
            D *p = new D;
            *p = tmp;
            v2.push_back(p);
        }
        tm1.stop();

        std::random_shuffle(v2.begin(), v2.end());

        tm2.start();
        for (size_t i = 0; i < v2.size(); ++i) {
            delete v2[i];
        }
        tm2.stop();
        
        printf("[%lu:%d] new<D>=%.1f delete<D>=%.1f\n",
                 pthread_self(), j, tm1.n_elapsed()/(double)N,
                 tm2.n_elapsed()/(double)N);
    }
    
    return NULL;
}

TEST_F(ObjectPoolTest, get_and_return_int_single_thread) {
    get_and_return_int(NULL);
    new_and_delete_int(NULL);
}

TEST_F(ObjectPoolTest, get_and_return_int_multiple_threads) {
    pthread_t tid[16];
    for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) {
        ASSERT_EQ(0, pthread_create(&tid[i], NULL, get_and_return_int, NULL));
    }
    for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) {
        pthread_join(tid[i], NULL);
    }

    pthread_t tid2[16];
    for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) {
        ASSERT_EQ(0, pthread_create(&tid2[i], NULL, new_and_delete_int, NULL));
    }
    for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) {
        pthread_join(tid2[i], NULL);
    }

    std::cout << describe_objects<D>() << std::endl;
    clear_objects<D>();
    ObjectPoolInfo info = describe_objects<D>();
    ObjectPoolInfo zero_info = { 0, 0, 0, 0, ObjectPoolBlockMaxItem<D>::value,
                                 ObjectPoolBlockMaxItem<D>::value, 0 };
    ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info)));
}

TEST_F(ObjectPoolTest, verify_get) {
    clear_objects<int>();
    std::cout << describe_objects<int>() << std::endl;
                              
    std::vector<int*> v;
    v.reserve(100000);
    for (int i = 0; (size_t)i < v.capacity(); ++i)  {
        int* p = get_object<int>();
        *p = i;
        v.push_back(p);
    }
    int i;
    for (i = 0; (size_t)i < v.size() && *v[i] == i; ++i);
    ASSERT_EQ(v.size(), (size_t)i) << "i=" << i << ", " << *v[i];

    clear_objects<int>();
}
} // namespace