/*
 * (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/eckit_config.h"

#include "eckit/memory/Counted.h"
#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 {

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

struct FooLock : public OwnedLock
{
    typedef SharedPtr<FooLock> ptype;

    FooLock( int in ) : i(in) {}
    int i;
};

struct FooNoLock : public OwnedNoLock
{
    typedef SharedPtr<FooNoLock> ptype;

    FooNoLock( int in ) : i(in) {}
    int i;
};

template <typename F>
void TestDefault () {

    typename F::ptype p;
    EXPECT( !p );
    EXPECT( p.get() == 0 );

    p.reset( new F(10) );

    EXPECT( p );
    EXPECT( p.unique() );

    p.release();

    EXPECT( !p );
    EXPECT( p.owners() == 0 );

}

template <typename F>
void TestCopy () {

    typename F::ptype p1;
    typename F::ptype p2;
    typename F::ptype p3;

    EXPECT( !p1 );
    EXPECT( p1.owners() == 0 );

    p1.reset( new F(10) );

    EXPECT( p1->i == 10 );

    p1->i = 20;

    EXPECT( p1 != p2 );
    EXPECT( p1 != p3 );
    EXPECT( p2 == p3 );

    EXPECT(  p1 );
    EXPECT( !p2 );
    EXPECT( !p3 );

    EXPECT( p1.owners() == 1 );
    EXPECT( p2.owners() == 0 );
    EXPECT( p3.owners() == 0 );

    p2.reset(p1);

    EXPECT( p1->i == 20 );
    EXPECT( p2->i == 20 );

    p1->i = 30;

    EXPECT( p1->i == 30 );
    EXPECT( p2->i == 30 );

    EXPECT( p1 == p2 );
    EXPECT( p1 != p3 );
    EXPECT( p2 != p3 );

    EXPECT(  p1 );
    EXPECT(  p2 );
    EXPECT( !p3 );

    EXPECT( p1.owners() == 2 );
    EXPECT( p2.owners() == 2 );
    EXPECT( p3.owners() == 0 );

    p3 = p1;

    p1->i = 40;

    EXPECT( p1->i == 40 );
    EXPECT( p2->i == 40 );
    EXPECT( p3->i == 40 );

    EXPECT( p1 == p2 );
    EXPECT( p1 == p3 );
    EXPECT( p2 == p3 );

    EXPECT( p1 );
    EXPECT( p2 );
    EXPECT( p3 );

    EXPECT( p1.owners() == 3 );
    EXPECT( p2.owners() == 3 );
    EXPECT( p3.owners() == 3 );

    p1.release();

    EXPECT( !p1 );
    EXPECT(  p2 );
    EXPECT(  p3 );

    EXPECT( p1.owners() == 0 );
    EXPECT( p2.owners() == 2 );
    EXPECT( p3.owners() == 2 );

    p2.release();

    EXPECT( !p1 );
    EXPECT( !p2 );
    EXPECT(  p3 );

    EXPECT( p1.owners() == 0 );
    EXPECT( p2.owners() == 0 );
    EXPECT( p3.owners() == 1 );

    p3.release();

    EXPECT( !p1 );
    EXPECT( !p2 );
    EXPECT( !p3 );

    EXPECT( p1.owners() == 0 );
    EXPECT( p2.owners() == 0 );
    EXPECT( p3.owners() == 0 );

}

template <typename F>
void TestRelease( )
{
    typename F::ptype p;

    EXPECT( !p );

    p.release();

    EXPECT( !p );

    p.reset( new F(10) );

    EXPECT( p );

    p.release();

    EXPECT( !p );
}

template <typename F>
void TestSwap( )
{
    typename F::ptype p1( new F(10) );
    typename F::ptype p2( new F(5) );

    EXPECT( p1 );
    EXPECT( p2 );

    EXPECT( p1.unique() );
    EXPECT( p2.unique() );


    EXPECT( p1->i == 10 );
    EXPECT( p2->i == 5 );

    p1.swap(p2);

    EXPECT( p1->i == 5 );
    EXPECT( p2->i == 10 );

    EXPECT( p1.unique() );
    EXPECT( p2.unique() );

    EXPECT( p1 );
    EXPECT( p2 );
}

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

CASE ( "test_default" ) {
    SETUP( "" ) {
        SECTION( "Type FooLock") {
            TestDefault< FooLock > ();
        }
        SECTION( "Type FooNoLock") {
            TestDefault< FooNoLock > ();
        }
    }
}

CASE ( "test_copy" ) {
    SETUP( "" ) {
        SECTION( "Type FooLock") {
            TestCopy< FooLock > ();
        }
        SECTION( "Type FooNoLock") {
            TestCopy< FooNoLock > ();
        }
    }
}

CASE ( "test_release" ) {
    SETUP( "" ) {
        SECTION( "Type FooLock") {
            TestRelease< FooLock > ();
        }
        SECTION( "Type FooNoLock") {
            TestRelease< FooNoLock > ();
        }
    }
}

CASE ( "test_swap" ) {
    SETUP( "" ) {
        SECTION( "Type FooLock") {
            TestSwap< FooLock > ();
        }
        SECTION( "Type FooNoLock") {
            TestSwap< FooNoLock > ();
        }
    }
}

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

} // namespace test
} // namespace eckit

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