//
// 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 <boost/config.hpp>
#ifdef BOOST_MSVC
#pragma warning( disable: 4996 )
#endif

#include <boost/test/unit_test.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/functional/hash.hpp>
#include <boost/ptr_container/ptr_container.hpp>
#include <boost/ptr_container/serialize_ptr_container.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/string.hpp>
#include <fstream>
#include <string>
#include <cstdio>

//
// serialization helper: we can't save a non-const object
// 
template< class T >
inline T const& as_const( T const& r )
{
    return r;
}

//
// used to customize tests for circular_buffer
//
template< class Cont >
struct set_capacity
{
    void operator()( Cont& ) const
    { }
};

template<class T>
struct set_capacity< boost::ptr_circular_buffer<T> >
{
    void operator()( boost::ptr_circular_buffer<T>& c ) const
    {
        c.set_capacity( 100u ); 
    }
};

//
// class hierarchy
// 
struct Base
{
    friend class boost::serialization::access;

    int i;

    
    template< class Archive >
    void serialize( Archive& ar, const unsigned int /*version*/ )
    {
        ar & boost::serialization::make_nvp( "i", i );
    }

    Base() : i(42)
    { }
    
    Base( int i ) : i(i)
    { }
    
    virtual ~Base()
    { }
};

inline bool operator<( const Base& l, const Base& r )
{
    return l.i < r.i;
}

inline bool operator==( const Base& l, const Base& r )
{
    return l.i == r.i;
}

inline std::size_t hash_value( const Base& b )
{
    return boost::hash_value( b.i );
}

struct Derived : Base
{
    int i2;

    template< class Archive >
    void serialize( Archive& ar, const unsigned int /*version*/ )
    {
        ar & boost::serialization::make_nvp( "Base",  
                 boost::serialization::base_object<Base>( *this ) );
        ar & boost::serialization::make_nvp( "i2", i2 );
    }

    Derived() : Base(42), i2(42)
    { }
    
    explicit Derived( int i2 ) : Base(0), i2(i2)
    { }
};

BOOST_CLASS_EXPORT_GUID( Derived, "Derived" )

//
// test of containers
// 
// 

template< class C, class T >
void add( C& c, T* r, unsigned /*n*/ )
{
    c.insert( c.end(), r ); 
}

template< class U, class T >
void add( boost::ptr_array<U,2>& c, T* r, unsigned n )
{
    c.replace( n, r );
}

template< class Cont, class OArchive, class IArchive >
void test_serialization_helper()
{
    Cont vec;
    set_capacity<Cont>()( vec );
    add( vec, new Base( -1 ), 0u );
    add( vec, new Derived( 1 ), 1u );
    BOOST_CHECK_EQUAL( vec.size(), 2u );

    std::string fn = std::tmpnam( 0 );

    {
        std::ofstream ofs( fn.c_str() );
        OArchive oa(ofs);
        oa << boost::serialization::make_nvp( "container", as_const(vec) );
    }

    Cont vec2;

    {
        std::ifstream ifs( fn.c_str(), std::ios::binary );
        IArchive ia(ifs);
        ia >> boost::serialization::make_nvp( "container", vec2 );
    }

    std::remove( fn.c_str() );

    BOOST_CHECK_EQUAL( vec.size(), vec2.size() );
    BOOST_CHECK_EQUAL( (*vec2.begin()).i, -1 );
    BOOST_CHECK_EQUAL( (*++vec2.begin()).i, 0 );

    typename Cont::iterator i = vec2.begin();
    ++i;
    Derived* d = dynamic_cast<Derived*>( &*i ); 
    BOOST_CHECK_EQUAL( d->i2, 1 );

}

template< class Cont, class OArchive, class IArchive >
void test_serialization_unordered_set_helper()
{
    Cont vec;
    set_capacity<Cont>()( vec );
    add( vec, new Base( -1 ), 0u );
    add( vec, new Derived( 1 ), 1u );
    BOOST_CHECK_EQUAL( vec.size(), 2u );

    std::string fn = std::tmpnam( 0 );

    {
        std::ofstream ofs( fn.c_str() );
        OArchive oa(ofs);
        oa << boost::serialization::make_nvp( "container", as_const(vec) );
    }

    Cont vec2;

    {
        std::ifstream ifs( fn.c_str(), std::ios::binary );
        IArchive ia(ifs);
        ia >> boost::serialization::make_nvp( "container", vec2 );
    }

    std::remove( fn.c_str() );

    BOOST_CHECK_EQUAL( vec.size(), vec2.size() );
    BOOST_CHECK_EQUAL( (*vec2.begin()).i, -1 );
    BOOST_CHECK_EQUAL( (*++vec2.begin()).i, 0 );
}

template< class Map, class OArchive, class IArchive >
void test_serialization_map_helper()
{
    Map m;
    std::string key1("key1"), key2("key2");
    m.insert( key1, new Base( -1 ) );
    m.insert( key2, new Derived( 1 ) );
    BOOST_CHECK_EQUAL( m.size(), 2u );

    std::string fn = std::tmpnam( 0 );

    {
        std::ofstream ofs( fn.c_str() );
        OArchive oa(ofs);
        oa << boost::serialization::make_nvp( "container", as_const(m) );
    }

    Map m2;

    {
        std::ifstream ifs( fn.c_str(), std::ios::binary );
        IArchive ia(ifs);
        ia >> boost::serialization::make_nvp( "container", m2 );
    }

    std::remove( fn.c_str() );

    BOOST_CHECK_EQUAL( m.size(), m2.size() );
    BOOST_CHECK_EQUAL( m2.find(key1)->second->i, -1 );
    BOOST_CHECK_EQUAL( m2.find(key2)->second->i, 0 );

    typename Map::iterator i = m2.find(key2);
    Derived* d = dynamic_cast<Derived*>( i->second ); 
    BOOST_CHECK_EQUAL( d->i2, 1 ); 
}

//
// basic test of hierarchy
// 
void test_hierarchy()
{
    Base* p = new Derived();

    std::string fn = std::tmpnam( 0 );

    {
        std::ofstream ofs( fn.c_str() );
        boost::archive::text_oarchive oa(ofs);
        oa << as_const(p);
    }
    
    Base* d = 0; 

    {
        std::ifstream ifs( fn.c_str(), std::ios::binary );
        boost::archive::text_iarchive ia(ifs);
        ia >> d;
    }
    
    std::remove( fn.c_str() );

    BOOST_CHECK_EQUAL( p->i, d->i );
    BOOST_CHECK( p != d );
    BOOST_CHECK( dynamic_cast<Derived*>( d ) );
    delete p;
    delete d;
} 

//
// test initializer
// 
void test_serialization()
{          
    test_hierarchy();
    test_serialization_helper< boost::ptr_deque<Base>,
                               boost::archive::text_oarchive,
                               boost::archive::text_iarchive >();
    test_serialization_helper< boost::ptr_list<Base>,
                               boost::archive::text_oarchive,
                               boost::archive::text_iarchive>();
    test_serialization_helper< boost::ptr_vector<Base>, 
                               boost::archive::text_oarchive, 
                               boost::archive::text_iarchive>();
    test_serialization_helper< boost::ptr_vector<Base>, 
                               boost::archive::xml_oarchive, 
                               boost::archive::xml_iarchive>();
    test_serialization_helper< boost::ptr_circular_buffer<Base>, 
                               boost::archive::text_oarchive, 
                               boost::archive::text_iarchive>();
    test_serialization_helper< boost::ptr_circular_buffer<Base>, 
                               boost::archive::xml_oarchive, 
                               boost::archive::xml_iarchive>();
    test_serialization_helper< boost::ptr_array<Base,2>, 
                               boost::archive::text_oarchive,
                               boost::archive::text_iarchive>();
    test_serialization_helper< boost::ptr_set<Base>,
                               boost::archive::text_oarchive,
                               boost::archive::text_iarchive>();
    test_serialization_helper< boost::ptr_multiset<Base>, 
                               boost::archive::text_oarchive,
                               boost::archive::text_iarchive>();
    
    test_serialization_unordered_set_helper< boost::ptr_unordered_set<Base>,
                                             boost::archive::text_oarchive,
                                             boost::archive::text_iarchive>();
   test_serialization_unordered_set_helper<boost::ptr_unordered_multiset<Base>, 
                                           boost::archive::text_oarchive,
                                           boost::archive::text_iarchive>();
                               
    test_serialization_map_helper< boost::ptr_map<std::string,Base>, 
                                   boost::archive::text_oarchive,
                                   boost::archive::text_iarchive>();
    test_serialization_map_helper< boost::ptr_multimap<std::string,Base>, 
                                   boost::archive::text_oarchive,
                                   boost::archive::text_iarchive>();

    test_serialization_map_helper< boost::ptr_map<std::string,Base>, 
                                   boost::archive::xml_oarchive,
                                   boost::archive::xml_iarchive>();
    test_serialization_map_helper< boost::ptr_multimap<std::string,Base>, 
                                   boost::archive::xml_oarchive,
                                   boost::archive::xml_iarchive>();

    test_serialization_map_helper< boost::ptr_unordered_map<std::string,Base>, 
                                   boost::archive::text_oarchive,
                                   boost::archive::text_iarchive>();
    test_serialization_map_helper< boost::ptr_unordered_multimap<std::string,Base>, 
                                   boost::archive::text_oarchive,
                                   boost::archive::text_iarchive>();

    test_serialization_map_helper< boost::ptr_unordered_map<std::string,Base>, 
                                   boost::archive::xml_oarchive,
                                   boost::archive::xml_iarchive>();
    test_serialization_map_helper< boost::ptr_unordered_multimap<std::string,Base>, 
                                   boost::archive::xml_oarchive,
                                   boost::archive::xml_iarchive>();

}


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_serialization ) );

    return test;
}