//  Copyright (C) 2011 Tim Blechmann
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)

// enables error checks via dummy::~dtor
#define BOOST_LOCKFREE_FREELIST_INIT_RUNS_DTOR

#include <boost/lockfree/detail/freelist.hpp>
#include <boost/lockfree/queue.hpp>

#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/scoped_ptr.hpp>

#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
#include <boost/test/included/unit_test.hpp>
#else
#include <boost/test/unit_test.hpp>
#endif

#include <set>

#include "test_helpers.hpp"

using boost::lockfree::detail::atomic;

atomic<bool> test_running(false);

struct dummy
{
    dummy(void)
    {
        if (test_running.load(boost::lockfree::detail::memory_order_relaxed))
            assert(allocated == 0);
        allocated = 1;
    }

    ~dummy(void)
    {
        if (test_running.load(boost::lockfree::detail::memory_order_relaxed))
            assert(allocated == 1);
        allocated = 0;
    }

    size_t padding[2]; // for used for the freelist node
    int allocated;
};

template <typename freelist_type,
          bool threadsafe,
          bool bounded>
void run_test(void)
{
    freelist_type fl(std::allocator<int>(), 8);

    std::set<dummy*> nodes;

    dummy d;
    if (bounded)
        test_running.store(true);

    for (int i = 0; i != 4; ++i) {
        dummy * allocated = fl.template construct<threadsafe, bounded>();
        BOOST_REQUIRE(nodes.find(allocated) == nodes.end());
        nodes.insert(allocated);
    }

    BOOST_FOREACH(dummy * d, nodes)
        fl.template destruct<threadsafe>(d);

    nodes.clear();
    for (int i = 0; i != 4; ++i)
        nodes.insert(fl.template construct<threadsafe, bounded>());

    BOOST_FOREACH(dummy * d, nodes)
        fl.template destruct<threadsafe>(d);

    for (int i = 0; i != 4; ++i)
        nodes.insert(fl.template construct<threadsafe, bounded>());

    if (bounded)
        test_running.store(false);
}

template <bool bounded>
void run_tests(void)
{
    run_test<boost::lockfree::detail::freelist_stack<dummy>, true, bounded>();
    run_test<boost::lockfree::detail::freelist_stack<dummy>, false, bounded>();
    run_test<boost::lockfree::detail::fixed_size_freelist<dummy>, true, bounded>();
}

BOOST_AUTO_TEST_CASE( freelist_tests )
{
    run_tests<false>();
    run_tests<true>();
}

template <typename freelist_type, bool threadsafe>
void oom_test(void)
{
    const bool bounded = true;
    freelist_type fl(std::allocator<int>(), 8);

    for (int i = 0; i != 8; ++i)
        fl.template construct<threadsafe, bounded>();

    dummy * allocated = fl.template construct<threadsafe, bounded>();
    BOOST_REQUIRE(allocated == NULL);
}

BOOST_AUTO_TEST_CASE( oom_tests )
{
    oom_test<boost::lockfree::detail::freelist_stack<dummy>, true >();
    oom_test<boost::lockfree::detail::freelist_stack<dummy>, false >();
    oom_test<boost::lockfree::detail::fixed_size_freelist<dummy>, true >();
    oom_test<boost::lockfree::detail::fixed_size_freelist<dummy>, false >();
}


template <typename freelist_type, bool bounded>
struct freelist_tester
{
    static const int size = 128;
    static const int thread_count = 4;
#ifndef BOOST_LOCKFREE_STRESS_TEST
    static const int operations_per_thread = 1000;
#else
    static const int operations_per_thread = 100000;
#endif

    freelist_type fl;
    boost::lockfree::queue<dummy*> allocated_nodes;

    atomic<bool> running;
    static_hashed_set<dummy*, 1<<16 > working_set;


    freelist_tester(void):
        fl(std::allocator<int>(), size), allocated_nodes(256)
    {}

    void run()
    {
        running = true;

        if (bounded)
            test_running.store(true);
        boost::thread_group alloc_threads;
        boost::thread_group dealloc_threads;

        for (int i = 0; i != thread_count; ++i)
            dealloc_threads.create_thread(boost::bind(&freelist_tester::deallocate, this));

        for (int i = 0; i != thread_count; ++i)
            alloc_threads.create_thread(boost::bind(&freelist_tester::allocate, this));
        alloc_threads.join_all();
        test_running.store(false);
        running = false;
        dealloc_threads.join_all();
    }

    void allocate(void)
    {
        for (long i = 0; i != operations_per_thread; ++i)  {
            for (;;) {
                dummy * node = fl.template construct<true, bounded>();
                if (node) {
                    bool success = working_set.insert(node);
                    assert(success);
                    allocated_nodes.push(node);
                    break;
                }
            }
        }
    }

    void deallocate(void)
    {
        for (;;) {
            dummy * node;
            if (allocated_nodes.pop(node)) {
                bool success = working_set.erase(node);
                assert(success);
                fl.template destruct<true>(node);
            }

            if (running.load() == false)
                break;
        }

        dummy * node;
        while (allocated_nodes.pop(node)) {
            bool success = working_set.erase(node);
            assert(success);
            fl.template destruct<true>(node);
        }
    }
};

template <typename Tester>
void run_tester()
{
    boost::scoped_ptr<Tester> tester (new Tester);
    tester->run();
}


BOOST_AUTO_TEST_CASE( unbounded_freelist_test )
{
    typedef freelist_tester<boost::lockfree::detail::freelist_stack<dummy>, false > test_type;
    run_tester<test_type>();
}


BOOST_AUTO_TEST_CASE( bounded_freelist_test )
{
    typedef freelist_tester<boost::lockfree::detail::freelist_stack<dummy>, true > test_type;
    run_tester<test_type>();
}

BOOST_AUTO_TEST_CASE( fixed_size_freelist_test )
{
    typedef freelist_tester<boost::lockfree::detail::fixed_size_freelist<dummy>, true > test_type;
    run_tester<test_type>();
}
