/*
 * (C) Copyright 1996-2017 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include <cmath>

#include "eckit/memory/Owned.h"
#include "eckit/memory/SharedPtr.h"
#include "eckit/log/Log.h"
#include "eckit/runtime/Tool.h"

#include "eckit/testing/Test.h"

using namespace std;
using namespace eckit;
using namespace eckit::testing;

namespace eckit {
namespace test {

//-----------------------------------------------------------------------------

class Incomplete;

Incomplete * get_ptr(  eckit::SharedPtr<Incomplete>& incomplete )
{
  return incomplete.get();
}

template<class T>
void ck( const T* v1, T v2 ) { EXPECT( *v1 == v2 ); }

namespace {
   int UDT_use_count;  // independent of pointer maintained counts
}

//-----------------------------------------------------------------------------
//  user defined type

class UDT : public OwnedNoLock {
public:
    typedef SharedPtr<UDT> ptype;

    UDT( long in ) : value_(in) {  ++UDT_use_count; }
    ~UDT() {
       --UDT_use_count;
//       std::cout << "   UDT with value " << value_ << " being destroyed\n";
    }

    long value() const { return value_; }
    void value( long v ) { value_ = v;; }
private:
    long value_;
};

//-----------------------------------------------------------------------------
//  tests on incomplete types

//  Certain smart pointer operations are specified to work on incomplete types,
//  and some uses depend upon this feature.  These tests verify compilation
//  only - the functions aren't actually invoked.

class Incomplete;

Incomplete * check_incomplete( eckit::SharedPtr<Incomplete>& incomplete,
                                 eckit::SharedPtr<Incomplete>& i2 )
{
   incomplete.swap(i2);
   //std::cout << incomplete.use_count() << ' ' << incomplete.unique() << '\n';
   return incomplete.get();
}

//-----------------------------------------------------------------------------

template <typename T >
eckit::SharedPtr<T> add_another_shareptr( T* p )
{
	return eckit::SharedPtr<T>( p );
}

//-----------------------------------------------------------------------------

// TODO issues:
// o/ test shared ptr, in STL containers
// o/ <Not applicable> weak shared ptr
// o/ <Not applicable> enable_shared_from_this/ not required
// o/ <Not applicable> custom deletor
// o/ <Not applicable> ? static pointer cast
//
// eckit::SharedPtr has null() member function, this does not exist on the standard/boost

//-----------------------------------------------------------------------------

CASE ( "test_intrusive_shared_ptr" )
{

//   std::cout << "test SharedPtr with a user defined type\n";
   {
      UDT_use_count = 0;
      UDT * up = new UDT(0);
      EXPECT( up->value() == 0 );

	  eckit::SharedPtr<UDT> sup1 ( up );
	  EXPECT( up == sup1.get() );
	  EXPECT( sup1.use_count() == 1 );
	  EXPECT( sup1.unique());

	  sup1->value( 54321 ) ;
	  EXPECT( sup1->value() == 54321 );
      EXPECT( up->value() == 54321 );

      eckit::SharedPtr<UDT> sup2;
	  sup2 = sup1;
      EXPECT( sup2->value() == 54321 );
	  EXPECT( sup1.use_count() == 2 );
      EXPECT( sup2.use_count() == 2 );
      EXPECT( !sup2.unique());
	  EXPECT( !sup1.unique());

//      cout << "eckit::SharedPtr check self assignment\n";
      sup2 = sup2;
      EXPECT( sup2->value() == 54321 );
	  EXPECT( sup1.use_count() == 2 );
      EXPECT( sup2.use_count() == 2 );

	  // check return creates from function

	  eckit::SharedPtr<UDT> sup3 = add_another_shareptr( up );
	  EXPECT( sup3->value() == 54321 );
	  EXPECT( sup1.use_count() == 3 );
	  EXPECT( sup2.use_count() == 3 );
	  EXPECT( sup3.use_count() == 3 );

   }

    EXPECT( UDT_use_count == 0 );

   {
//      std::cout << "test SharedPtr swap\n";
	  eckit::SharedPtr<UDT> sup1 ( new UDT(0) );
	  EXPECT(sup1.get() != 0);
	  EXPECT(sup1.use_count() == 1 );
	  EXPECT(sup1.unique());

      eckit::SharedPtr<UDT> sup2;
      EXPECT(sup2.use_count() == 0 );
      EXPECT(sup2.get() == 0);

	  sup1.swap(sup2);

      EXPECT(sup2.get() != 0);
      EXPECT(sup2.use_count() == 1 );
      EXPECT(sup2.unique());

	  EXPECT(sup1.use_count() == 0 );
	  EXPECT(sup1.get() == 0);
   }

   EXPECT( UDT_use_count == 0 );


   //   std::cout << "test SharedPtr with a user defined type in std::vector\n";
   {
      std::vector< eckit::SharedPtr<UDT> > vec;
      vec.push_back(eckit::SharedPtr<UDT>(new UDT(0)));
      vec.push_back(eckit::SharedPtr<UDT>(new UDT(1)));
      vec.push_back(eckit::SharedPtr<UDT>(new UDT(2)));
   }
   EXPECT( UDT_use_count == 0 );

   //   std::cout << "test SharedPtr with a user defined type in std::set\n";
   {
      std::set< eckit::SharedPtr<UDT> > vec;
      vec.insert(eckit::SharedPtr<UDT>(new UDT(0)));
      vec.insert(eckit::SharedPtr<UDT>(new UDT(1)));
      vec.insert(eckit::SharedPtr<UDT>(new UDT(3)));
   }
   EXPECT( UDT_use_count == 0 );

   //   std::cout << "test SharedPtr with a user defined type in std::map\n";
   {
      std::map<std::string, eckit::SharedPtr<UDT> > map;
      map.insert(std::make_pair(std::string("first"),eckit::SharedPtr<UDT>(new UDT(0))));
      map.insert(std::make_pair(std::string("secon"),eckit::SharedPtr<UDT>(new UDT(1))));
      map.insert(std::make_pair(std::string("third"),eckit::SharedPtr<UDT>(new UDT(2))));
   }
   EXPECT( UDT_use_count == 0 );

}

//-----------------------------------------------------------------------------

} // namespace test
} // namespace eckit

int main(int argc,char **argv)
{
    return run_tests ( argc, argv );
}
