//
// Boost.Pointer Container
//
//  Copyright Thorsten Ottosen 2003-2005. Use, modification and
//  distribution is subject to 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)
//
// For more information, see http://www.boost.org/libs/ptr_container/
//

#include "test_data.hpp"
#include <boost/test/unit_test.hpp>
#include <boost/ptr_container/exception.hpp>
#include <boost/ptr_container/detail/ptr_container_disable_deprecated.hpp>
#include <boost/range/sub_range.hpp>
#include <boost/cast.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>

//
// abstract base class definition
// 
struct abstract_base
{
    virtual ~abstract_base() {}
    virtual void foo() = 0;
    virtual abstract_base* clone() const = 0;
};

struct implementation : abstract_base
{
    implementation()
    { }

    implementation( const implementation& )
    { }
    
    implementation( int, std::string, int, std::string )
    { }

    virtual void foo() {}
    virtual abstract_base* clone() const
    {
        return new implementation( *this );
    }
};

inline std::ostream& operator<<( std::ostream& out, const abstract_base& r )
{
    return out;    
}

inline abstract_base* new_clone( const abstract_base& r )
{
    return r.clone();
}

//
// ptr_map test
// 

template< typename C, typename B, typename T >
void ptr_map_test();

template< class Key >
Key get_next_key( const Key& k );

template<>
int get_next_key<int>( const int& )
{
    return rand();
}

template<>
std::string get_next_key<std::string>( const std::string& )
{
    return boost::lexical_cast<std::string>( rand() );
}

#if defined(BOOST_PTR_CONTAINER_DISABLE_DEPRECATED)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

template< typename C, typename B, typename T >
void ptr_map_test()
{
    using namespace boost;

    BOOST_TEST_MESSAGE( "starting associative container test" );
    enum { max_cnt = 10, size = 100 };
    C  c;
    BOOST_CHECK( c.size() == 0 );

    const C c2( c.begin(), c.end() );
    BOOST_CHECK( c.size() == c2.size() );

    C c3;

    BOOST_TEST_MESSAGE( "finished construction test" );

    BOOST_DEDUCED_TYPENAME C::allocator_type alloc        = c.get_allocator();
    BOOST_DEDUCED_TYPENAME C::iterator i                  = c.begin();
    BOOST_DEDUCED_TYPENAME C::const_iterator ci           = c2.begin();
    BOOST_DEDUCED_TYPENAME C::iterator i2                 = c.end();
    hide_warning(i2);
    BOOST_DEDUCED_TYPENAME C::const_iterator ci2          = c2.begin();
    hide_warning(ci2);
    BOOST_DEDUCED_TYPENAME C::reverse_iterator ri         = c.rbegin();
    hide_warning(ri);
    BOOST_DEDUCED_TYPENAME C::const_reverse_iterator cri  = c2.rbegin();
    hide_warning(cri);
    BOOST_DEDUCED_TYPENAME C::reverse_iterator rv2        = c.rend();
    hide_warning(rv2);
    BOOST_DEDUCED_TYPENAME C::const_reverse_iterator cvr2 = c2.rend();
    hide_warning(cvr2);

    BOOST_DEDUCED_TYPENAME C::key_type a_key;

    BOOST_TEST_MESSAGE( "finished iterator test" );

    BOOST_DEDUCED_TYPENAME C::size_type s                 = c.size();
    BOOST_DEDUCED_TYPENAME C::size_type s2                = c.max_size();
    hide_warning(s2);
    BOOST_CHECK_EQUAL( c.size(), s );
    bool b                                                = c.empty();
    hide_warning(b);
    BOOST_TEST_MESSAGE( "finished accessors test" );

    a_key = get_next_key( a_key );
    c.insert( a_key, new T );
    a_key = get_next_key( a_key );
    c.insert( a_key, new T );
    c3.insert( c.begin(), c.end() );
    c.insert( c3 );
    c.erase( c.begin() );
    BOOST_CHECK( c3.end() == c3.erase( boost::make_iterator_range(c3) ) );
    c3.erase( a_key );

    BOOST_CHECK( c3.empty() );
    c.swap( c3 );
    swap(c,c3);
    swap(c3,c);
    BOOST_CHECK( !c3.empty() );
    c3.clear();
    BOOST_CHECK( c3.empty() );
    BOOST_TEST_MESSAGE( "finished modifiers test" );


    a_key = get_next_key( a_key );
    c.insert( a_key, new T );
    a_key = get_next_key( a_key );
#ifndef BOOST_NO_AUTO_PTR
    c.insert( a_key, std::auto_ptr<T>( new T ) );
#endif
#ifndef BOOST_NO_CXX11_SMART_PTR
    c.insert( a_key, std::unique_ptr<T>( new T ) );
#endif
    typename C::auto_type ptr2  = c.release( c.begin() );
#ifndef BOOST_NO_AUTO_PTR
    std::auto_ptr<C> ap         = c.release();
#else
    std::unique_ptr<C> up       = c.release();
#endif
    c                           = c2.clone();
    BOOST_TEST_MESSAGE( "finished release/clone test" );


    a_key = get_next_key( a_key );
    c3.insert( a_key, new T );
    a_key = get_next_key( a_key );
    c3.insert( a_key, new T );

    c. BOOST_NESTED_TEMPLATE transfer<C>( c3.begin(), c3 );
    c. BOOST_NESTED_TEMPLATE transfer<C>( c3.begin(), c3.end(), c3 );
    BOOST_CHECK( c3.empty() );
    BOOST_CHECK( !c.empty() );
    c3. BOOST_NESTED_TEMPLATE transfer<C>( c );
    BOOST_CHECK( !c3.empty() );
    BOOST_CHECK( c.empty() );
#ifdef BOOST_NO_SFINAE
#else
    c. BOOST_NESTED_TEMPLATE transfer<C>( make_iterator_range(c3), c3 );
    BOOST_CHECK( !c.empty() );
    BOOST_CHECK( c3.empty() );
    c3. BOOST_NESTED_TEMPLATE transfer<C>(c);
#endif
    BOOST_TEST_MESSAGE( "finished transfer test" );

    BOOST_CHECK( !c3.empty() );
    c3.replace( c3.begin(), new T );
#ifndef BOOST_NO_AUTO_PTR
    c3.replace( c3.begin(), std::auto_ptr<T>( new T ) );
#endif
#ifndef BOOST_NO_CXX11_SMART_PTR
    c3.replace( c3.begin(), std::unique_ptr<T>( new T ) );
#endif
    BOOST_TEST_MESSAGE( "finished set/map interface test" );

    // @todo: make macro with algorithms so that the right erase() is called.
    //  c.unique();
    //  c.unique( std::not_equal_to<T>() );
    //  c.remove( T() );
    //  c.remove_if( std::binder1st< std::equal_to<T> >( T() ) );

    sub_range<C>        sub;
    sub_range<const C> csub;

    i  = c.find( get_next_key( a_key ) );
    ci = c2.find( get_next_key( a_key ) );
    c2.count( get_next_key( a_key ) );
    i  = c.lower_bound( get_next_key( a_key ) );
    ci = c2.lower_bound( get_next_key( a_key ) );
    i  = c.upper_bound( get_next_key( a_key ) );
    ci = c2.upper_bound( get_next_key( a_key ) );
    sub  = c.equal_range( get_next_key( a_key ) );
    csub = c2.equal_range( get_next_key( a_key ) );

    try
    {
        c.at( get_next_key( a_key ) );
    }
    catch( const bad_ptr_container_operation& )
    { }

    try
    {
        c2.at( get_next_key( a_key ) );
    }
    catch( const bad_ptr_container_operation& )
    { }

    BOOST_TEST_MESSAGE( "finished algorithms interface test" );

    typename C::iterator it = c.begin(), e = c.end();
    for( ; it != e; ++it )
    {
        std::cout << "\n mapped value = " << *it->second << " key = " << it->first;
        //std::cout << "\n mapped value = " << it.value() << " key = " << it.key();
    }
    
    typename C::reverse_iterator rit = c.rbegin(), re = c.rend();
    for( ; rit != re; ++rit )
    {
        std::cout << "\n mapped value = " << *rit->second << " key = " << rit->first;
        //std::cout << "\n mapped value = " << rit.value() << " key = " << rit.key();    
        //std::cout << "\n mapped value (base) = " 
        //          << rit.base().value() << " key = " << rit.base().key();   
    }
    
    typename C::const_reverse_iterator crit = c2.rbegin(), cre = c2.rend();
    for( ; crit != cre; ++crit )
    {
        std::cout << "\n mapped value = " << *(*crit).second << " key = " << (*crit).first;
        //std::cout << "\n mapped value = " << crit.value() << " key = " << crit.key();    
        //std::cout << "\n mapped value (base) = " 
        //          << crit.base().value() << " key = " << crit.base().key();   
    }
    
    BOOST_TEST_MESSAGE( "finished iterator test" );

    a_key = get_next_key( a_key );
    c.insert( a_key, new T );
    c.erase( a_key );
    c.erase( a_key );

}

#if defined(BOOST_PTR_CONTAINER_DISABLE_DEPRECATED)
#pragma GCC diagnostic pop
#endif



template< class CDerived, class CBase, class T >
void test_transfer()
{
    CDerived from;
    CBase    to;

    int key = get_next_key( key );
    from.insert( key, new T );
    key = get_next_key( key );
    from.insert( key, new T );
    transfer_test( from, to );
}



template< class BaseContainer, class DerivedContainer, class Derived >
void map_container_assignment_test()
{
    DerivedContainer derived;
    std::string foo( "foo" );
    std::string bar( "foo" );
    derived.insert( foo, new Derived );
    derived.insert( bar, new Derived );

    BaseContainer base_container( derived );
    BOOST_CHECK_EQUAL( derived.size(), base_container.size() );
    base_container.clear();
    base_container = derived;
    BOOST_CHECK_EQUAL( derived.size(), base_container.size() );
    
    BaseContainer base2( base_container );
    BOOST_CHECK_EQUAL( base2.size(), base_container.size() );
    base2 = base_container;
    BOOST_CHECK_EQUAL( base2.size(), base_container.size() );
    base_container = base_container;
}



#include <boost/ptr_container/ptr_map.hpp>

using namespace std;

void test_map()
{
    ptr_map_test< ptr_map<int, Base>, Base, Derived_class >();
    ptr_map_test< ptr_map<int, Value>, Value, Value >();
    ptr_map_test< ptr_map<int, nullable<Base> >, Base, Derived_class >();
    ptr_map_test< ptr_map<int, nullable<Value> >, Value, Value >();
    ptr_map_test< ptr_map<int, abstract_base>, abstract_base, implementation >();

    ptr_map_test< ptr_multimap<int,Base>, Base, Derived_class >();
    ptr_map_test< ptr_multimap<int,Value>, Value, Value >();    
    ptr_map_test< ptr_multimap<int, nullable<Base> >, Base, Derived_class >();
    ptr_map_test< ptr_multimap<int, nullable<Value> >, Value, Value >();

    map_container_assignment_test< ptr_map<std::string,Base>,
                                   ptr_map<std::string,Derived_class>,
                                   Derived_class>();
    map_container_assignment_test< ptr_map<std::string, nullable<Base> >,
                                   ptr_map<std::string,Derived_class>,
                                   Derived_class>();                            
    map_container_assignment_test< ptr_map<std::string, nullable<Base> >,
                                   ptr_map<std::string, nullable<Derived_class> >, 
                                   Derived_class>();                               
   map_container_assignment_test< ptr_multimap<std::string,Base>,
                                   ptr_multimap<std::string,Derived_class>,
                                   Derived_class>();
    map_container_assignment_test< ptr_multimap<std::string, nullable<Base> >,
                                   ptr_multimap<std::string,Derived_class>,
                                   Derived_class>();                            
    map_container_assignment_test< ptr_multimap<std::string, nullable<Base> >,
                                   ptr_multimap<std::string, nullable<Derived_class> >, 
                                   Derived_class>();                               

                                      
    test_transfer< ptr_map<int,Derived_class>, ptr_map<int,Base>, Derived_class >();
    test_transfer< ptr_multimap<int,Derived_class>, ptr_multimap<int,Base>, Derived_class >();
    
    string joe   = "joe";
    string brian = "brian";
    string kenny = "kenny"; 
    
    ptr_map<string,int> m;
    m.insert( joe, new int( 4 ) );
    m.insert( brian, new int( 6 ) );
    BOOST_CHECK( m[ "foo" ] == 0 );
    m[ "bar" ] += 5;
    BOOST_CHECK( m[ "bar" ] == 5 );
    m[ joe ]   += 56;
    m[ brian ] += 10;

    BOOST_CHECK_THROW( (m.insert(kenny, 0 )), bad_ptr_container_operation );
    BOOST_CHECK_THROW( (m.replace(m.begin(), 0 )), bad_ptr_container_operation ); 
    BOOST_CHECK_THROW( (m.at("not there")), bad_ptr_container_operation );

    for( ptr_map<string,int>::iterator i = m.begin();
         i != m.end(); ++i )
    {
        if( is_null(i) )
            BOOST_CHECK( false );
        const string& ref  = i->first;
        hide_warning(ref);
        int&          ref2 = *(*i).second;
        ref2++;
    }

    typedef ptr_map<string,Derived_class> map_type;
    map_type m2;
    m2.insert( joe, new Derived_class );
    //
    // This works fine since 'm2' is not const
    //
    m2.begin()->second->foo();

    //
    // These all return an implementation-defined proxy
    // with two public members: 'first' and 'second'
    //
    map_type::value_type       a_value      = *m2.begin();
    a_value.second->foo();
    map_type::reference        a_reference  = *m2.begin();
    a_reference.second->foo();
    map_type::const_reference  a_creference = *const_begin(m2);
    hide_warning(a_creference);
   
    //
    //
    // These will fail as iterators propagate constness
    // 
    //a_creference.second->foo();
    //a_cpointer->second->foo();
    //const_begin(m2)->second->foo();

}

#include <boost/tuple/tuple.hpp>
#include <boost/iterator/zip_iterator.hpp>
#include <map>
#include <boost/ptr_container/ptr_map.hpp>

void test_map_iterators()
{
    using boost::zip_iterator;
    using boost::tuple;
    using boost::make_tuple;
    using boost::ptr_map;
    using std::map;

    //typedef map<int, int> theMapType;
    /*
    @remark: the following will not compile
             because of the proxy (non-reference) returned by operator*()
             of the ptr_map's iterator type.
             
    typedef boost::ptr_map<int, int> theMapType;
    typedef zip_iterator
        <tuple<theMapType::iterator, theMapType::iterator> > zipIter;
    theMapType map1;
    theMapType map2;
    zipIter zip(make_tuple(map1.begin(), map2.begin()));
    */
}



using boost::unit_test::test_suite;

test_suite* init_unit_test_suite( int argc, char* argv[] )
{
    test_suite* test = BOOST_TEST_SUITE( "Pointer Container Test Suite" );

    test->add( BOOST_TEST_CASE( &test_map ) );
    test->add( BOOST_TEST_CASE( &test_map_iterators ) );

    return test;
}
