//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2008. 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)
//
// See http://www.boost.org/libs/container for documentation.
//
//////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_CONTAINER_PROPAGATE_ALLOCATOR_TEST_HPP
#define BOOST_CONTAINER_PROPAGATE_ALLOCATOR_TEST_HPP

#include <boost/container/detail/config_begin.hpp>
#include <boost/core/lightweight_test.hpp>
#include "dummy_test_allocator.hpp"

#include <iostream>

namespace boost{
namespace container {
namespace test{

template<class Selector>
struct alloc_propagate_base;

template<class T, class Allocator, class Selector>
class alloc_propagate_wrapper
   : public alloc_propagate_base<Selector>::template apply<T, Allocator>::type
{
   BOOST_COPYABLE_AND_MOVABLE(alloc_propagate_wrapper)

   public:   
   typedef typename alloc_propagate_base
      <Selector>::template apply<T, Allocator>::type  Base;

   typedef typename Base::allocator_type  allocator_type;
   typedef typename Base::value_type      value_type;
   typedef typename Base::size_type       size_type;

   alloc_propagate_wrapper()
      : Base()
   {}

   explicit alloc_propagate_wrapper(const allocator_type &a)
      : Base(a)
   {}
/*
   //sequence containers only
   explicit alloc_propagate_wrapper(size_type n, const value_type &v, const allocator_type &a)
      : Base(n, v, a)
   {}

   alloc_propagate_wrapper(size_type n, const allocator_type &a)
      : Base(n, a)
   {}*/

   template<class Iterator>
   alloc_propagate_wrapper(Iterator b, Iterator e, const allocator_type &a)
      : Base(b, e, a)
   {}

   #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST)
   alloc_propagate_wrapper(std::initializer_list<value_type> il, const allocator_type& a)
      : Base(il, a)
   {}
/*
   //associative containers only
   alloc_propagate_wrapper(std::initializer_list<value_type> il, const Compare& comp, const allocator_type& a)
      : Base(il, comp, a)
   {}*/

   #endif

   alloc_propagate_wrapper(const alloc_propagate_wrapper &x)
      : Base(x)
   {}

   alloc_propagate_wrapper(const alloc_propagate_wrapper &x, const allocator_type &a)
      : Base(x, a)
   {}

   alloc_propagate_wrapper(BOOST_RV_REF(alloc_propagate_wrapper) x)
      : Base(boost::move(static_cast<Base&>(x)))
   {}

   alloc_propagate_wrapper(BOOST_RV_REF(alloc_propagate_wrapper) x, const allocator_type &a)
      : Base(boost::move(static_cast<Base&>(x)), a)
   {}

   alloc_propagate_wrapper &operator=(BOOST_COPY_ASSIGN_REF(alloc_propagate_wrapper) x)
   {  this->Base::operator=((const Base &)x);  return *this; }

   alloc_propagate_wrapper &operator=(BOOST_RV_REF(alloc_propagate_wrapper) x)
   {  this->Base::operator=(boost::move(static_cast<Base&>(x)));  return *this; }

   void swap(alloc_propagate_wrapper &x)
   {  this->Base::swap(x);  }
};

template<class T>
struct get_real_stored_allocator
{
   typedef typename T::stored_allocator_type type;
};

template<class Container>
void test_propagate_allocator_allocator_arg();

template<class Selector>
bool test_propagate_allocator()
{
   {
      typedef propagation_test_allocator<char, true, true, true, true, true>  AlwaysPropagate;
      typedef alloc_propagate_wrapper<char, AlwaysPropagate, Selector>  PropagateCont;
      typedef typename get_real_stored_allocator<typename PropagateCont::Base>::type StoredAllocator;
      {
         //////////////////////////////////////////
         //Test AlwaysPropagate allocator propagation
         //////////////////////////////////////////
         
         //default constructor
         StoredAllocator::reset_unique_id(111);
         PropagateCont c;  //stored 112
         BOOST_TEST (c.get_stored_allocator().id_ == 112);
         BOOST_TEST (c.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().ctr_moves_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c.get_stored_allocator().swaps_ == 0);
      }
      {
         //copy constructor
         StoredAllocator::reset_unique_id(222);
         PropagateCont c; //stored 223
         BOOST_TEST (c.get_stored_allocator().id_ == 223);
         //propagate_on_copy_constructor produces copies, moves or RVO (depending on the compiler).
         //For allocators that copy in select_on_container_copy_construction, at least we must have a copy
         PropagateCont c2(c); //should propagate 223
         BOOST_TEST (c2.get_stored_allocator().id_ == 223);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_ >= 1);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      {
         //move constructor
         StoredAllocator::reset_unique_id(333);
         PropagateCont c; //stored 334
         BOOST_TEST (c.get_stored_allocator().id_ == 334);
         PropagateCont c2(boost::move(c)); //should propagate 334
         BOOST_TEST (c2.get_stored_allocator().id_ == 334);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().ctr_moves_ > 0);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      {
         //copy assign
         StoredAllocator::reset_unique_id(444);
         PropagateCont c; //stored 445
         BOOST_TEST (c.get_stored_allocator().id_ == 445);
         PropagateCont c2; //stored 446
         BOOST_TEST (c2.get_stored_allocator().id_ == 446);
         c2 = c; //should propagate 445
         BOOST_TEST (c2.get_stored_allocator().id_ == 445);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().ctr_moves_  == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 1);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      {
         //move assign
         StoredAllocator::reset_unique_id(555);
         PropagateCont c; //stored 556
         BOOST_TEST (c.get_stored_allocator().id_ == 556);
         PropagateCont c2; //stored 557
         BOOST_TEST (c2.get_stored_allocator().id_ == 557);
         c = boost::move(c2); //should propagate 557
         BOOST_TEST (c.get_stored_allocator().id_ == 557);
         BOOST_TEST (c.get_stored_allocator().ctr_copies_    == 0);
         BOOST_TEST (c.get_stored_allocator().ctr_moves_     == 0);
         BOOST_TEST (c.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_moves_ == 1);
         BOOST_TEST (c.get_stored_allocator().swaps_ == 0);
      }
      {
         //swap
         StoredAllocator::reset_unique_id(666);
         PropagateCont c; //stored 667
         BOOST_TEST (c.get_stored_allocator().id_ == 667);
         PropagateCont c2; //stored 668
         BOOST_TEST (c2.get_stored_allocator().id_ == 668);
         c.swap(c2);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_    == 0);
         BOOST_TEST (c.get_stored_allocator().ctr_copies_    == 0);
         BOOST_TEST (c2.get_stored_allocator().ctr_moves_     == 0);
         BOOST_TEST (c.get_stored_allocator().ctr_moves_     == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_  == 0);
         BOOST_TEST (c.get_stored_allocator().assign_moves_  == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_  == 1);
         BOOST_TEST (c.get_stored_allocator().swaps_  == 1);
      }
      //And now allocator argument constructors
      test_propagate_allocator_allocator_arg<PropagateCont>();
   }

   //////////////////////////////////////////
   //Test NeverPropagate allocator propagation
   //////////////////////////////////////////
   {
      typedef propagation_test_allocator<char, false, false, false, false, true> NeverPropagate;
      typedef alloc_propagate_wrapper<char, NeverPropagate, Selector>      NoPropagateCont;
      typedef typename get_real_stored_allocator<typename NoPropagateCont::Base>::type StoredAllocator;
      {
         //default constructor
         StoredAllocator::reset_unique_id(111);
         NoPropagateCont c; //stored 112
         BOOST_TEST (c.get_stored_allocator().id_ == 112);
         BOOST_TEST (c.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().ctr_moves_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c.get_stored_allocator().swaps_ == 0);
      }
      {
         //copy constructor
         //propagate_on_copy_constructor produces copies, moves or RVO (depending on the compiler)
         //For allocators that don't copy in select_on_container_copy_construction we must have a default
         //construction
         StoredAllocator::reset_unique_id(222);
         NoPropagateCont c; //stored 223
         BOOST_TEST (c.get_stored_allocator().id_ == 223);
         NoPropagateCont c2(c); //should NOT propagate 223
         BOOST_TEST (c2.get_stored_allocator().id_ == 224);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      {
         //move constructor
         StoredAllocator::reset_unique_id(333);
         NoPropagateCont c; //stored 334
         BOOST_TEST (c.get_stored_allocator().id_ == 334);
         NoPropagateCont c2(boost::move(c)); // should NOT propagate 334
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      {
         //copy assign
         StoredAllocator::reset_unique_id(444);
         NoPropagateCont c; //stored 445
         NoPropagateCont c2; //stored 446
         c2 = c; // should NOT propagate 445
         BOOST_TEST (c2.get_stored_allocator().id_ == 446);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().ctr_moves_  == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      {
         //move assign
         StoredAllocator::reset_unique_id(555);
         NoPropagateCont c; //stored 556
         NoPropagateCont c2; //stored 557
         c2 = c; // should NOT propagate 556
         BOOST_TEST (c2.get_stored_allocator().id_ == 557);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().ctr_moves_  == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
      }
      //And now allocator argument constructors
      test_propagate_allocator_allocator_arg<NoPropagateCont>();
   }
   {
      //Don't use unequal ids as unequal allocators -------------------------|,
      //because swap requires equal allocators                               v
      typedef propagation_test_allocator<char, false, false, false, false, false> NeverPropagate;
      typedef alloc_propagate_wrapper<char, NeverPropagate, Selector>      NoPropagateCont;
      typedef typename get_real_stored_allocator<typename NoPropagateCont::Base>::type StoredAllocator;
      {
         //swap
         StoredAllocator::reset_unique_id(666);
         NoPropagateCont c; //stored 667
         BOOST_TEST (c.get_stored_allocator().id_ == 667);
         NoPropagateCont c2; //stored 668
         BOOST_TEST (c2.get_stored_allocator().id_ == 668);
         c2.swap(c); // should NOT swap 667 and 668
         BOOST_TEST (c2.get_stored_allocator().id_ == 668);
         BOOST_TEST (c2.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().ctr_moves_  == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
         BOOST_TEST (c.get_stored_allocator().id_ == 667);
         BOOST_TEST (c.get_stored_allocator().ctr_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().ctr_moves_  == 0);
         BOOST_TEST (c.get_stored_allocator().assign_copies_ == 0);
         BOOST_TEST (c.get_stored_allocator().assign_moves_ == 0);
         BOOST_TEST (c.get_stored_allocator().swaps_ == 0);
      }
   }

   return report_errors() == 0;
}

template<class Container>
void test_propagate_allocator_allocator_arg()
{
   typedef typename Container::allocator_type         allocator_type;
   typedef typename get_real_stored_allocator<typename Container::Base>::type StoredAllocator;

   {  //The allocator must be always propagated
      //allocator constructor
      allocator_type::reset_unique_id(111);
      const allocator_type & a = allocator_type(); //stored 112
      Container c(a); //should propagate 112
      BOOST_TEST (c.get_stored_allocator().id_ == 112);
      BOOST_TEST (c.get_stored_allocator().ctr_copies_ > 0);
      BOOST_TEST (c.get_stored_allocator().ctr_moves_ == 0);
      BOOST_TEST (c.get_stored_allocator().assign_copies_ == 0);
      BOOST_TEST (c.get_stored_allocator().assign_moves_ == 0);
      BOOST_TEST (c.get_stored_allocator().swaps_ == 0);
   }
   {
      //copy allocator constructor
      StoredAllocator::reset_unique_id(999);
      Container c;
      //stored_allocator_type could be the same type as allocator_type
      //so reset it again to get a predictable result
      allocator_type::reset_unique_id(222);
      Container c2(c, allocator_type()); //should propagate 223
      BOOST_TEST (c2.get_stored_allocator().id_ == 223);
      BOOST_TEST (c2.get_stored_allocator().ctr_copies_ > 0);
      BOOST_TEST (c2.get_stored_allocator().ctr_moves_ == 0);
      BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
      BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
      BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
   }
   {
      //move allocator constructor
      StoredAllocator::reset_unique_id(999);
      Container c;
      //stored_allocator_type could be the same type as allocator_type
      //so reset it again to get a predictable result
      allocator_type::reset_unique_id(333);
      Container c2(boost::move(c), allocator_type()); //should propagate 334
      BOOST_TEST (c2.get_stored_allocator().id_ == 334);
      BOOST_TEST (c2.get_stored_allocator().ctr_copies_ > 0);
      BOOST_TEST (c2.get_stored_allocator().ctr_moves_ == 0);
      BOOST_TEST (c2.get_stored_allocator().assign_copies_ == 0);
      BOOST_TEST (c2.get_stored_allocator().assign_moves_ == 0);
      BOOST_TEST (c2.get_stored_allocator().swaps_ == 0);
   }
}

}  //namespace test{
}  //namespace container {
}  //namespace boost{

#include <boost/container/detail/config_end.hpp>

#endif   //#ifndef BOOST_CONTAINER_PROPAGATE_ALLOCATOR_TEST_HPP
