//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2014-2014.
// 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/move for documentation.
//
//////////////////////////////////////////////////////////////////////////////
#include <boost/move/adl_move_swap.hpp>
#include <boost/move/core.hpp>
#include <boost/core/lightweight_test.hpp>

class swap_stats
{
   public:
   static void reset_stats()
   {
      member_swap_calls = 0;
      friend_swap_calls = 0;
      move_cnstor_calls = 0;
      move_assign_calls = 0;
      copy_cnstor_calls = 0;
      copy_assign_calls = 0;
   }

   static unsigned int member_swap_calls;
   static unsigned int friend_swap_calls;
   static unsigned int move_cnstor_calls;
   static unsigned int move_assign_calls;
   static unsigned int copy_cnstor_calls;
   static unsigned int copy_assign_calls;
};

unsigned int swap_stats::member_swap_calls = 0;
unsigned int swap_stats::friend_swap_calls = 0;
unsigned int swap_stats::move_cnstor_calls = 0;
unsigned int swap_stats::move_assign_calls = 0;
unsigned int swap_stats::copy_cnstor_calls = 0;
unsigned int swap_stats::copy_assign_calls = 0;

class movable : public swap_stats
{
   BOOST_MOVABLE_BUT_NOT_COPYABLE(movable)
   public:
   movable()                                 {}
   movable(BOOST_RV_REF(movable))            { ++move_cnstor_calls; }
   movable & operator=(BOOST_RV_REF(movable)){ ++move_assign_calls; return *this; }
   friend void swap(movable &, movable &)    { ++friend_swap_calls; }
};

class movable_swap_member : public swap_stats
{
   BOOST_MOVABLE_BUT_NOT_COPYABLE(movable_swap_member)
   public:
   movable_swap_member()                                             {}
   movable_swap_member(BOOST_RV_REF(movable_swap_member))            { ++move_cnstor_calls; }
   movable_swap_member & operator=(BOOST_RV_REF(movable_swap_member)){ ++move_assign_calls; return *this; }
   void swap(movable_swap_member &)                                  { ++member_swap_calls; }
   friend void swap(movable_swap_member &, movable_swap_member &)    { ++friend_swap_calls; }
};

class copyable : public swap_stats
{
   public:
   copyable()                                {}
   copyable(const copyable &)                { ++copy_cnstor_calls; }
   copyable & operator=(const copyable&)     { ++copy_assign_calls; return *this; }
   void swap(copyable &)                     { ++member_swap_calls; }
   friend void swap(copyable &, copyable &)  { ++friend_swap_calls; }
};

class no_swap : public swap_stats
{
   private: unsigned m_state;
   public:
   explicit no_swap(unsigned i): m_state(i){}
   no_swap(const no_swap &x)               { m_state = x.m_state; ++copy_cnstor_calls; }
   no_swap & operator=(const no_swap& x)   { m_state = x.m_state; ++copy_assign_calls; return *this; }
   void swap(no_swap &)                    { ++member_swap_calls; }
   friend bool operator==(const no_swap &x, const no_swap &y) {  return x.m_state == y.m_state; }
   friend bool operator!=(const no_swap &x, const no_swap &y) {  return !(x==y); }
};


int main()
{
   {  //movable
      movable x, y;
      swap_stats::reset_stats();
      ::boost::adl_move_swap(x, y);
      #if defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
      //In non rvalue reference compilers,
      //movable classes with no swap() member uses
      //boost::move() to implement swap.
      BOOST_TEST(swap_stats::friend_swap_calls == 0);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::move_cnstor_calls == 1);
      BOOST_TEST(swap_stats::move_assign_calls == 2);
      BOOST_TEST(swap_stats::copy_cnstor_calls == 0);
      BOOST_TEST(swap_stats::copy_assign_calls == 0);
      #else
      //In compilers with rvalue references, this should call friend swap via ADL
      BOOST_TEST(swap_stats::friend_swap_calls == 1);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::move_cnstor_calls == 0);
      BOOST_TEST(swap_stats::move_assign_calls == 0);
      BOOST_TEST(swap_stats::copy_cnstor_calls == 0);
      BOOST_TEST(swap_stats::copy_assign_calls == 0);
      #endif
   }
   {  //movable_swap_member
      movable_swap_member x, y;
      swap_stats::reset_stats();
      ::boost::adl_move_swap(x, y);
      #if defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
      //In non rvalue reference compilers,
      //movable classes with no swap() member uses
      //boost::move() to implement swap.
      BOOST_TEST(swap_stats::friend_swap_calls == 0);
      BOOST_TEST(swap_stats::member_swap_calls == 1);
      BOOST_TEST(swap_stats::move_cnstor_calls == 0);
      BOOST_TEST(swap_stats::move_assign_calls == 0);
      BOOST_TEST(swap_stats::copy_cnstor_calls == 0);
      BOOST_TEST(swap_stats::copy_assign_calls == 0);
      #else
      //In compilers with rvalue references, this should call friend swap via ADL
      BOOST_TEST(swap_stats::friend_swap_calls == 1);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::move_cnstor_calls == 0);
      BOOST_TEST(swap_stats::move_assign_calls == 0);
      BOOST_TEST(swap_stats::copy_cnstor_calls == 0);
      BOOST_TEST(swap_stats::copy_assign_calls == 0);
      #endif
   }
   {  //copyable
      copyable x, y;
      swap_stats::reset_stats();
      ::boost::adl_move_swap(x, y);
      //This should call friend swap via ADL
      BOOST_TEST(swap_stats::friend_swap_calls == 1);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::move_cnstor_calls == 0);
      BOOST_TEST(swap_stats::move_assign_calls == 0);
      BOOST_TEST(swap_stats::copy_cnstor_calls == 0);
      BOOST_TEST(swap_stats::copy_assign_calls == 0);
   }
   {  //no_swap
      no_swap x(1), y(2), x_back(x), y_back(y);
      swap_stats::reset_stats();
      ::boost::adl_move_swap(x, y);
      //This should call std::swap which uses copies
      BOOST_TEST(swap_stats::friend_swap_calls == 0);
      BOOST_TEST(swap_stats::member_swap_calls == 0);
      BOOST_TEST(swap_stats::move_cnstor_calls == 0);
      BOOST_TEST(swap_stats::move_assign_calls == 0);
      BOOST_TEST(swap_stats::copy_cnstor_calls == 1);
      BOOST_TEST(swap_stats::copy_assign_calls == 2);
      BOOST_TEST(x == y_back);
      BOOST_TEST(y == x_back);
      BOOST_TEST(x != y);
   }
   return ::boost::report_errors();
}
