// Copyright (c) 2006-2018 Maxim Khizhinsky
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)

#include "map_type.h"
#include <cds/os/topology.h>

namespace map {

    namespace {
        struct key_thread
        {
            uint32_t  nKey;
            uint16_t  nThread;

            key_thread( size_t key, size_t threadNo )
                : nKey( static_cast<uint32_t>(key))
                , nThread( static_cast<uint16_t>(threadNo))
            {}

            key_thread()
                : nKey()
                , nThread()
            {}
        };

        static_assert(sizeof( key_thread ) % 8 == 0, "Key size mismatch!!!");
    } // namespace

    template <>
    struct cmp<key_thread> {
        int operator ()(key_thread const& k1, key_thread const& k2) const
        {
            if ( k1.nKey < k2.nKey )
                return -1;
            if ( k1.nKey > k2.nKey )
                return 1;
            if ( k1.nThread < k2.nThread )
                return -1;
            if ( k1.nThread > k2.nThread )
                return 1;
            return 0;
        }
        int operator ()(key_thread const& k1, size_t k2) const
        {
            if ( k1.nKey < k2 )
                return -1;
            if ( k1.nKey > k2 )
                return 1;
            return 0;
        }
        int operator ()(size_t k1, key_thread const& k2) const
        {
            if ( k1 < k2.nKey )
                return -1;
            if ( k1 > k2.nKey )
                return 1;
            return 0;
        }
    };

    template <>
    struct less<key_thread>
    {
        bool operator()( key_thread const& k1, key_thread const& k2 ) const
        {
            if ( k1.nKey <= k2.nKey )
                return k1.nKey < k2.nKey || k1.nThread < k2.nThread;
            return false;
        }
    };

    template <>
    struct hash<key_thread>
    {
        typedef size_t             result_type;
        typedef key_thread    argument_type;

        size_t operator()( key_thread const& k ) const
        {
            return std::hash<size_t>()(k.nKey);
        }
        size_t operator()( size_t k ) const
        {
            return std::hash<size_t>()(k);
        }
    };

    class Map_DelOdd: public cds_test::stress_fixture
    {
    public:
        static size_t s_nInsThreadCount;      // insert thread count
        static size_t s_nDelThreadCount;      // delete thread count
        static size_t s_nExtractThreadCount;  // extract thread count
        static size_t s_nMapSize;             // max map size
        static size_t s_nMaxLoadFactor;       // maximum load factor
        static size_t s_nInsertPassCount;
        static size_t s_nFindThreadCount;     // find thread count

        static size_t s_nCuckooInitialSize;       // initial size for CuckooMap
        static size_t s_nCuckooProbesetSize;      // CuckooMap probeset size (only for list-based probeset)
        static size_t s_nCuckooProbesetThreshold; // CuckooMap probeset threshold (0 - use default)

        static size_t s_nFeldmanMap_HeadBits;
        static size_t s_nFeldmanMap_ArrayBits;

        static size_t  s_nLoadFactor;  // current load factor

        static std::vector<size_t> m_arrElements;

        static void SetUpTestCase();
        static void TearDownTestCase();

        template <typename Pred>
        static void prepare_array( std::vector<size_t>& arr, Pred pred )
        {
            arr.reserve( m_arrElements.size());
            for ( auto el : m_arrElements ) {
                if ( pred( el ))
                    arr.push_back( el );
            }
            arr.resize( arr.size());
            shuffle( arr.begin(), arr.end());
        }

    protected:
        typedef key_thread  key_type;
        typedef size_t      value_type;
        typedef std::pair<key_type const, value_type> pair_type;

        atomics::atomic<size_t> m_nInsThreadCount;

        enum {
            inserter_thread,
            deleter_thread,
            extractor_thread,
            find_thread,
        };

        // Inserts keys from [0..N)
        template <class Map>
        class Inserter: public cds_test::thread
        {
            typedef cds_test::thread base_class;
            Map&     m_Map;

            struct update_func
            {
                template <typename Q>
                void operator()( bool /*bNew*/, Q const& ) const
                {}

                template <typename Q, typename V>
                void operator()( bool /*bNew*/, Q const&, V& ) const
                {}

                // FeldmanHashMap
                template <typename Q>
                void operator()( Q&, Q*) const
                {}
            };

            void init_data()
            {
                prepare_array( m_arr, []( size_t ) -> bool { return true; } );
                for ( size_t i = 0; i < m_arr.size(); ++i ) {
                    if ( m_Map.insert( key_type( m_arr[i], id())))
                        ++m_nInsertInitSuccess;
                    else
                        ++m_nInsertInitFailed;
                }
            }

        public:
            size_t m_nInsertSuccess = 0;
            size_t m_nInsertFailed = 0;
            size_t m_nInsertInitSuccess = 0;
            size_t m_nInsertInitFailed = 0;

            std::vector<size_t> m_arr;

        public:
            Inserter( cds_test::thread_pool& pool, Map& map )
                : base_class( pool, inserter_thread )
                , m_Map( map )
            {
                init_data();
            }

            Inserter( Inserter& src )
                : base_class( src )
                , m_Map( src.m_Map )
            {
                init_data();
            }

            virtual thread * clone()
            {
                return new Inserter( *this );
            }

            virtual void test()
            {
                Map& rMap = m_Map;
                Map_DelOdd& fixture = pool().template fixture<Map_DelOdd>();

                update_func f;

                for ( size_t nPass = 0; nPass < s_nInsertPassCount; ++nPass ) {
                    if ( nPass & 1 ) {
                        // insert pass
                        for ( auto el : m_arr ) {
                            if ( el & 1 ) {
                                if ( rMap.insert( key_type( el, id())))
                                    ++m_nInsertSuccess;
                                else
                                    ++m_nInsertFailed;
                            }
                        }
                    }
                    else {
                        // update pass
                        for ( auto el : m_arr ) {
                            if ( el & 1 ) {
                                bool success;
                                bool inserted;
                                std::tie( success, inserted ) = rMap.update( key_type( el, id()), f );
                                if ( success && inserted )
                                    ++m_nInsertSuccess;
                                else
                                    ++m_nInsertFailed;
                            }
                        }
                    }
                }

                fixture.m_nInsThreadCount.fetch_sub( 1, atomics::memory_order_release );
                m_arr.resize( 0 );
            }
        };

        struct key_equal {
            bool operator()( key_type const& k1, key_type const& k2 ) const
            {
                return k1.nKey == k2.nKey;
            }
            bool operator()( size_t k1, key_type const& k2 ) const
            {
                return k1 == k2.nKey;
            }
            bool operator()( key_type const& k1, size_t k2 ) const
            {
                return k1.nKey == k2;
            }
        };

        struct key_less {
            bool operator()( key_type const& k1, key_type const& k2 ) const
            {
                return k1.nKey < k2.nKey;
            }
            bool operator()( size_t k1, key_type const& k2 ) const
            {
                return k1 < k2.nKey;
            }
            bool operator()( key_type const& k1, size_t k2 ) const
            {
                return k1.nKey < k2;
            }

            typedef key_equal equal_to;
        };

        // Deletes odd keys from [0..N)
        template <class Map>
        class Deleter: public cds_test::thread
        {
            typedef cds_test::thread base_class;
            Map&     m_Map;

            void init_data()
            {
                prepare_array( m_arr, []( size_t el ) ->bool { return ( el & 1 ) != 0; } );
            }

        public:
            size_t  m_nDeleteSuccess = 0;
            size_t  m_nDeleteFailed = 0;

            std::vector<size_t> m_arr;

        public:
            Deleter( cds_test::thread_pool& pool, Map& map )
                : base_class( pool, deleter_thread )
                , m_Map( map )
            {
                init_data();
            }

            Deleter( Deleter& src )
                : base_class( src )
                , m_Map( src.m_Map )
            {
                init_data();
            }

            virtual thread * clone()
            {
                return new Deleter( *this );
            }

            virtual void test()
            {
                Map& rMap = m_Map;

                Map_DelOdd& fixture = pool().template fixture<Map_DelOdd>();
                size_t const nInsThreadCount = s_nInsThreadCount;

                do {
                    if ( id() & 1 ) {
                        for ( auto el: m_arr ) {
                            for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                                if ( rMap.erase( key_type( el, k )))
                                    ++m_nDeleteSuccess;
                                else
                                    ++m_nDeleteFailed;
                            }
                        }
                    }
                    else {
                        for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                            for ( auto el: m_arr ) {
                                if ( rMap.erase( key_type( el, k )))
                                    ++m_nDeleteSuccess;
                                else
                                    ++m_nDeleteFailed;
                            }
                        }
                    }
                } while ( fixture.m_nInsThreadCount.load( atomics::memory_order_acquire ) != 0 );

                m_arr.resize( 0 );
            }
        };

        // Deletes odd keys from [0..N)
        template <class GC, class Map >
        class Extractor: public cds_test::thread
        {
            typedef cds_test::thread base_class;
            Map&     m_Map;

            void init_data()
            {
                prepare_array( m_arr, []( size_t el ) ->bool { return ( el & 1 ) != 0; } );
            }

        public:
            size_t  m_nDeleteSuccess = 0;
            size_t  m_nDeleteFailed = 0;

            std::vector<size_t> m_arr;

        public:
            Extractor( cds_test::thread_pool& pool, Map& map )
                : base_class( pool, extractor_thread )
                , m_Map( map )
            {
                init_data();
            }

            Extractor( Extractor& src )
                : base_class( src )
                , m_Map( src.m_Map )
            {
                init_data();
            }

            virtual thread * clone()
            {
                return new Extractor( *this );
            }

            virtual void test()
            {
                Map& rMap = m_Map;

                typename Map::guarded_ptr gp;
                Map_DelOdd& fixture = pool().template fixture<Map_DelOdd>();
                size_t const nInsThreadCount = s_nInsThreadCount;

                do {
                    if ( id() & 1 ) {
                        for ( auto el : m_arr ) {
                            for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                                gp = rMap.extract( key_type( el, k ));
                                if ( gp )
                                    ++m_nDeleteSuccess;
                                else
                                    ++m_nDeleteFailed;
                                gp.release();
                            }
                        }
                    }
                    else {
                        for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                            for ( auto el: m_arr ) {
                                gp = rMap.extract( key_type( el, k ));
                                if ( gp )
                                    ++m_nDeleteSuccess;
                                else
                                    ++m_nDeleteFailed;
                                gp.release();
                            }
                        }
                    }
                } while ( fixture.m_nInsThreadCount.load( atomics::memory_order_acquire ) != 0 );

                m_arr.resize( 0 );
            }
        };

        template <class RCU, class Map >
        class Extractor< cds::urcu::gc<RCU>, Map > : public cds_test::thread
        {
            typedef cds_test::thread base_class;
            Map&     m_Map;

            void init_data()
            {
                prepare_array( m_arr, []( size_t el ) -> bool { return ( el & 1 ) != 0; } );
            }

        public:
            size_t  m_nDeleteSuccess = 0;
            size_t  m_nDeleteFailed = 0;

            std::vector<size_t> m_arr;

        public:
            Extractor( cds_test::thread_pool& pool, Map& map )
                : base_class( pool, extractor_thread )
                , m_Map( map )
            {
                init_data();
            }

            Extractor( Extractor& src )
                : base_class( src )
                , m_Map( src.m_Map )
            {
                init_data();
            }

            virtual thread * clone()
            {
                return new Extractor( *this );
            }

            virtual void test()
            {
                Map& rMap = m_Map;
                Map_DelOdd& fixture = pool().template fixture<Map_DelOdd>();

                typename Map::exempt_ptr xp;
                size_t const nInsThreadCount = s_nInsThreadCount;

                do {
                    if ( id() & 1 ) {
                        for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                            for ( auto el: m_arr ) {
                                if ( Map::c_bExtractLockExternal ) {
                                    typename Map::rcu_lock l;
                                    xp = rMap.extract( key_type( el, k ));
                                    if ( xp )
                                        ++m_nDeleteSuccess;
                                    else
                                        ++m_nDeleteFailed;
                                }
                                else {
                                    xp = rMap.extract( key_type( el, k ));
                                    if ( xp )
                                        ++m_nDeleteSuccess;
                                    else
                                        ++m_nDeleteFailed;
                                }
                                xp.release();
                            }
                        }
                    }
                    else {
                        for ( auto el : m_arr ) {
                            for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                                if ( Map::c_bExtractLockExternal ) {
                                    typename Map::rcu_lock l;
                                    xp = rMap.extract( key_type( el, k ));
                                    if ( xp )
                                        ++m_nDeleteSuccess;
                                    else
                                        ++m_nDeleteFailed;
                                }
                                else {
                                    xp = rMap.extract( key_type( el, k ));
                                    if ( xp )
                                        ++m_nDeleteSuccess;
                                    else
                                        ++m_nDeleteFailed;
                                }
                                xp.release();
                            }
                        }
                    }
                } while ( fixture.m_nInsThreadCount.load( atomics::memory_order_acquire ) != 0 );

                m_arr.resize( 0 );
            }
        };

        // Finds keys
        template <class Map>
        class Observer: public cds_test::thread
        {
            typedef cds_test::thread base_class;
            Map&                m_Map;

        public:
            size_t m_nFindEvenSuccess = 0;
            size_t m_nFindEvenFailed  = 0;
            size_t m_nFindOddSuccess  = 0;
            size_t m_nFindOddFailed   = 0;

        public:
            Observer( cds_test::thread_pool& pool, Map& map )
                : base_class( pool, find_thread )
                , m_Map( map )
            {}

            Observer( Observer& src )
                : base_class( src )
                , m_Map( src.m_Map )
            {}

            virtual thread * clone()
            {
                return new Observer( *this );
            }

            virtual void test()
            {
                Map& map = m_Map;
                Map_DelOdd& fixture = pool().template fixture<Map_DelOdd>();
                std::vector<size_t> const& arr = m_arrElements;
                size_t const nInsThreadCount = s_nInsThreadCount;

                do {
                    for ( size_t key : arr ) {
                        if ( key & 1 ) {
                            for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                                if ( map.contains( key_thread( key, k )))
                                    ++m_nFindOddSuccess;
                                else
                                    ++m_nFindOddFailed;
                            }
                        }
                        else {
                            // even keys MUST be in the map
                            for ( size_t k = 0; k < nInsThreadCount; ++k ) {
                                if ( map.contains( key_thread( key, k )))
                                    ++m_nFindEvenSuccess;
                                else
                                    ++m_nFindEvenFailed;
                            }
                        }
                    }
                } while ( fixture.m_nInsThreadCount.load( atomics::memory_order_acquire ) != 0 );
            }
        };

    protected:
        template <class Map>
        void do_test( Map& testMap )
        {
            typedef Inserter<Map> insert_thread;
            typedef Deleter<Map>  delete_thread;
            typedef Observer<Map> observer_thread;

            m_nInsThreadCount.store( s_nInsThreadCount, atomics::memory_order_release );

            cds_test::thread_pool& pool = get_pool();
            pool.add( new insert_thread( pool, testMap ), s_nInsThreadCount );
            pool.add( new delete_thread( pool, testMap ), s_nDelThreadCount ? s_nDelThreadCount : cds::OS::topology::processor_count());
            if ( s_nFindThreadCount )
                pool.add( new observer_thread( pool, testMap ), s_nFindThreadCount );

            propout() << std::make_pair( "insert_thread_count", s_nInsThreadCount )
                << std::make_pair( "delete_thread_count", s_nDelThreadCount )
                << std::make_pair( "find_thread_count", s_nFindThreadCount )
                << std::make_pair( "map_size", s_nMapSize )
                << std::make_pair( "pass_count", s_nInsertPassCount );

            std::chrono::milliseconds duration = pool.run();

            propout() << std::make_pair( "duration", duration );

            size_t nInsertInitFailed = 0;
            size_t nInsertInitSuccess = 0;
            size_t nInsertSuccess = 0;
            size_t nInsertFailed = 0;
            size_t nDeleteSuccess = 0;
            size_t nDeleteFailed = 0;

            size_t nFindEvenSuccess = 0;
            size_t nFindEvenFailed = 0;
            size_t nFindOddSuccess = 0;
            size_t nFindOddFailed = 0;

            for ( size_t i = 0; i < pool.size(); ++i ) {
                cds_test::thread& thr = pool.get( i );
                switch ( thr.type()) {
                case inserter_thread:
                    {
                        insert_thread& inserter = static_cast<insert_thread&>( thr );
                        nInsertSuccess += inserter.m_nInsertSuccess;
                        nInsertFailed += inserter.m_nInsertFailed;
                        nInsertInitSuccess += inserter.m_nInsertInitSuccess;
                        nInsertInitFailed += inserter.m_nInsertInitFailed;
                    }
                    break;
                case deleter_thread:
                    {
                        delete_thread& deleter = static_cast<delete_thread&>( thr );
                        nDeleteSuccess += deleter.m_nDeleteSuccess;
                        nDeleteFailed += deleter.m_nDeleteFailed;
                    }
                    break;
                case find_thread:
                    {
                        observer_thread& observer = static_cast<observer_thread&>( thr );
                        nFindEvenSuccess = observer.m_nFindEvenSuccess;
                        nFindEvenFailed = observer.m_nFindEvenFailed;
                        nFindOddSuccess = observer.m_nFindOddSuccess;
                        nFindOddFailed = observer.m_nFindOddFailed;
                    }
                    break;
                }
            }

            size_t const nInitialOddKeys = ( s_nMapSize * s_nInsThreadCount ) / 2;

            EXPECT_EQ( nInsertInitFailed, 0u );
            EXPECT_EQ( nInsertInitSuccess, s_nMapSize * s_nInsThreadCount );
            EXPECT_EQ( nFindEvenFailed, 0u );
            EXPECT_GE( nInsertSuccess + nInitialOddKeys, nDeleteSuccess );
            EXPECT_LE( nInsertSuccess, nDeleteSuccess );

            propout()
                << std::make_pair( "insert_init_success", nInsertInitSuccess )
                << std::make_pair( "insert_init_failed", nInsertInitFailed )
                << std::make_pair( "insert_success", nInsertSuccess )
                << std::make_pair( "insert_failed", nInsertFailed )
                << std::make_pair( "delete_success", nDeleteSuccess )
                << std::make_pair( "delete_failed", nDeleteFailed )
                << std::make_pair( "find_even_success", nFindEvenSuccess )
                << std::make_pair( "find_even_failed", nFindEvenFailed )
                << std::make_pair( "find_odd_success", nFindOddSuccess )
                << std::make_pair( "find_odd_failed", nFindOddFailed );

            analyze( testMap );
        }

        template <class Map>
        void do_test_extract( Map& testMap )
        {
            typedef Inserter<Map> insert_thread;
            typedef Deleter<Map> delete_thread;
            typedef Extractor< typename Map::gc, Map > extract_thread;
            typedef Observer<Map> observer_thread;

            m_nInsThreadCount.store( s_nInsThreadCount, atomics::memory_order_release );

            cds_test::thread_pool& pool = get_pool();
            pool.add( new insert_thread( pool, testMap ), s_nInsThreadCount );
            if ( s_nDelThreadCount )
                pool.add( new delete_thread( pool, testMap ), s_nDelThreadCount );
            if ( s_nExtractThreadCount )
                pool.add( new extract_thread( pool, testMap ), s_nExtractThreadCount );
            if ( s_nFindThreadCount )
                pool.add( new observer_thread( pool, testMap ), s_nFindThreadCount );

            propout() << std::make_pair( "insert_thread_count", s_nInsThreadCount )
                << std::make_pair( "delete_thread_count", s_nDelThreadCount )
                << std::make_pair( "extract_thread_count", s_nExtractThreadCount )
                << std::make_pair( "find_thread_count", s_nFindThreadCount )
                << std::make_pair( "map_size", s_nMapSize )
                << std::make_pair( "pass_count", s_nInsertPassCount );

            std::chrono::milliseconds duration = pool.run();

            propout() << std::make_pair( "duration", duration );

            size_t nInsertInitFailed = 0;
            size_t nInsertInitSuccess = 0;
            size_t nInsertSuccess = 0;
            size_t nInsertFailed = 0;
            size_t nDeleteSuccess = 0;
            size_t nDeleteFailed = 0;
            size_t nExtractSuccess = 0;
            size_t nExtractFailed = 0;

            size_t nFindEvenSuccess = 0;
            size_t nFindEvenFailed = 0;
            size_t nFindOddSuccess = 0;
            size_t nFindOddFailed = 0;

            for ( size_t i = 0; i < pool.size(); ++i ) {
                cds_test::thread& thr = pool.get( i );
                switch ( thr.type()) {
                case inserter_thread:
                {
                    insert_thread& inserter = static_cast<insert_thread&>(thr);
                    nInsertSuccess += inserter.m_nInsertSuccess;
                    nInsertFailed += inserter.m_nInsertFailed;
                    nInsertInitSuccess += inserter.m_nInsertInitSuccess;
                    nInsertInitFailed += inserter.m_nInsertInitFailed;
                }
                break;
                case deleter_thread:
                {
                    delete_thread& deleter = static_cast<delete_thread&>(thr);
                    nDeleteSuccess += deleter.m_nDeleteSuccess;
                    nDeleteFailed += deleter.m_nDeleteFailed;
                }
                break;
                case extractor_thread:
                {
                    extract_thread& extractor = static_cast<extract_thread&>(thr);
                    nExtractSuccess += extractor.m_nDeleteSuccess;
                    nExtractFailed += extractor.m_nDeleteFailed;
                }
                break;
                case find_thread:
                {
                    observer_thread& observer = static_cast<observer_thread&>( thr );
                    nFindEvenSuccess = observer.m_nFindEvenSuccess;
                    nFindEvenFailed = observer.m_nFindEvenFailed;
                    nFindOddSuccess = observer.m_nFindOddSuccess;
                    nFindOddFailed = observer.m_nFindOddFailed;
                }
                break;
                default:
                    assert( false );
                }
            }

            size_t const nInitialOddKeys = ( s_nMapSize * s_nInsThreadCount ) / 2;

            EXPECT_EQ( nInsertInitFailed, 0u );
            EXPECT_EQ( nInsertInitSuccess, s_nMapSize * s_nInsThreadCount );
            EXPECT_EQ( nFindEvenFailed, 0u );
            EXPECT_GE( nInsertSuccess + nInitialOddKeys, nDeleteSuccess + nExtractSuccess );
            EXPECT_LE( nInsertSuccess, nDeleteSuccess + nExtractSuccess );

            propout()
                << std::make_pair( "insert_init_success", nInsertInitSuccess )
                << std::make_pair( "insert_init_failed", nInsertInitFailed )
                << std::make_pair( "insert_success", nInsertSuccess )
                << std::make_pair( "insert_failed", nInsertFailed )
                << std::make_pair( "delete_success", nDeleteSuccess )
                << std::make_pair( "delete_failed", nDeleteFailed )
                << std::make_pair( "extract_success", nExtractSuccess )
                << std::make_pair( "extract_failed", nExtractFailed )
                << std::make_pair( "find_even_success", nFindEvenSuccess )
                << std::make_pair( "find_even_failed", nFindEvenFailed )
                << std::make_pair( "find_odd_success", nFindOddSuccess )
                << std::make_pair( "find_odd_failed", nFindOddFailed );

            analyze( testMap );
        }

        template <class Map>
        void analyze( Map& testMap )
        {
            // All even keys must be in the map
            {
                for ( size_t n = 0; n < s_nMapSize; n +=2 ) {
                    for ( size_t i = 0; i < s_nInsThreadCount; ++i ) {
                        EXPECT_TRUE( testMap.contains( key_type( n, i ))) << "key=" << n << "/" << i;
                    }
                }
            }

            print_stat( propout(), testMap );

            check_before_cleanup( testMap );
            testMap.clear();
            EXPECT_TRUE( testMap.empty()) << "map.size=" << testMap.size();

            additional_check( testMap );
            additional_cleanup( testMap );
        }

        template <class Map>
        void run_test_extract()
        {
            static_assert( Map::c_bExtractSupported, "Map class must support extract() method" );

            size_t nMapSize = s_nMapSize;
            s_nMapSize *= s_nInsThreadCount;

            Map testMap( *this );

            s_nMapSize = nMapSize;
            do_test_extract( testMap );
        }

        template <class Map>
        void run_test()
        {
            size_t nMapSize = s_nMapSize;
            s_nMapSize *= s_nInsThreadCount;

            Map testMap( *this );

            s_nMapSize = nMapSize;
            do_test( testMap );
        }

        template <class Map>
        void run_feldman();
    };

    class Map_DelOdd_LF: public Map_DelOdd
        , public ::testing::WithParamInterface<size_t>
    {
    public:
        template <class Map>
        void run_test()
        {
            s_nLoadFactor = GetParam();
            propout() << std::make_pair( "load_factor", s_nLoadFactor );
            Map_DelOdd::run_test<Map>();
        }

        template <class Map>
        void run_test_extract()
        {
            s_nLoadFactor = GetParam();
            propout() << std::make_pair( "load_factor", s_nLoadFactor );
            Map_DelOdd::run_test_extract<Map>();
        }

        static std::vector<size_t> get_load_factors();
    };

} // namespace map
