/*
 *
 *  Copyright (C) 2012-2017, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  ofstd
 *
 *  Author:  Jan Schlamelcher
 *
 *  Purpose: Implement fallback support for modern memory management
 *           approaches (e.g. smart pointers) for older compilers.
 *
 */

#ifndef OFMEM_H
#define OFMEM_H

#include "dcmtk/config/osconfig.h" // make sure OS specific configuration is included first
#include "dcmtk/ofstd/ofutil.h"

// use native classes when c++11 is supported
#if defined(HAVE_STL_MEMORY) && defined(HAVE_CXX11)

#include <memory>
template<typename... ARGS>
using OFshared_ptr = std::shared_ptr<ARGS...>;
template<typename... ARGS>
using OFunique_ptr = std::unique_ptr<ARGS...>;

#else //fallback implementations

#if defined HAVE_SYNC_ADD_AND_FETCH && defined HAVE_SYNC_SUB_AND_FETCH
#define OF_SHARED_PTR_COUNTER_TYPE size_t
#elif defined HAVE_INTERLOCKED_INCREMENT && defined HAVE_INTERLOCKED_DECREMENT
#if _MSC_VER <= 1200
#define OF_SHARED_PTR_COUNTER_TYPE LONG
#else
#define OF_SHARED_PTR_COUNTER_TYPE volatile LONG
#endif
#else
#define OF_SHARED_PTR_COUNTER_TYPE size_t
#define OF_SHARED_PTR_NEED_MUTEX 1
#include "dcmtk/ofstd/ofthread.h"
#endif

#ifdef HAVE_WINDOWS_H
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif

/** OFshared_ptr is a smart pointer that retains shared ownership of an object through a pointer.
 *  Several OFshared_ptr objects may own the same object; the object is destroyed when the last remaining
 *  OFshared_ptr referring to it is destroyed or reset.
 *
 *  An OFshared_ptr may also own no objects, in which case it is called empty.
 *
 *  OFshared_ptr meets the requirements of CopyConstructible and CopyAssignable.
 *  @tparam T the type of the managed object, e.g. int for an OFshared_ptr behaving like an int*.
 *  @note this implementation is meant to be a subset of the c++11's std::shared_ptr that lacks the
 *    following features: swap support, support for weak references, atomic access to the managed object,
 *                        custom deleters and some functions like comparing OFshared_ptrs or read access
 *                        to the reference counter.
 *    see http://en.cppreference.com/w/cpp/memory/shared_ptr to compare OFshared_ptr against std::shared_ptr.
 */
template<typename T>
class OFshared_ptr
{
public:
    /** Constructs a shared_ptr with pt as the managed object.
     *  @param pt the managed object that this shared_ptr takes
     *    the ownership to.
     */
    OFshared_ptr( T* const pt = OFnullptr )
    : m_pData( new Data( pt ) )
    {

    }

    /** Constructs a shared_ptr which shares ownership of the object managed by other.
     *  If other manages no object, *this manages no object too.
     *  @param other another shared_ptr that this one will be copy constructed from.
     */
    OFshared_ptr( const OFshared_ptr& other )
    : m_pData( other.m_pData )
    {
        m_pData->increment_count();
    }

    /** Replaces the managed object with the one managed by other.
     *  @param other another shared_ptr that this one will be copy assigned from.
     */
    OFshared_ptr& operator=( const OFshared_ptr& other )
    {
        if( this != &other )
        {
            destroy();
            m_pData = other.m_pData;
            m_pData->increment_count();
        }
        return *this;
    }

    /** If *this owns an object and it is the last shared_ptr owning it,
     *  the object is destroyed. Otherwise does nothing.
     */
    ~OFshared_ptr()
    {
        destroy();
    }

    /** Checks if *this manages an object, i.e. whether get() != 0.
     *  @return OFTrue if *this manages an object, OFFalse otherwise.
     */
    operator OFBool() const
    {
        return m_pData->pointer() != OFnullptr;
    }

    /** Checks if *this does not manage an object, i.e. whether get() == 0.
     *  @return OFFalse if *this manages an object, OFTrue otherwise.
     */
    OFBool operator!() const
    {
        return !m_pData->pointer();
    }

    /** Dereferences pointer to the managed object.
     *  @return a reference to the managed object.
     */
    T& operator*() const
    {
        return *m_pData->pointer();
    }

    /** Dereferences pointer to the managed object.
     *  @return a pointer to the managed object.
     */
    T* operator->() const
    {
        return m_pData->pointer();
    }

    /** Returns a pointer to the managed object.
     *  @return a pointer to the managed object.
     */
    T* get() const
    {
        return m_pData->pointer();
    }

    /** Replaces the managed object with an object pointed to by pt.
     *  @param pt pointer to an object to acquire ownership of.
     */
    void reset( T* const pt = OFnullptr )
    {
        destroy();
        m_pData = new Data( pt );
    }

private:
    /** Helper class containing the reference to the
     *  managed object and the reference counter.
     */
    class Data
    {
    public:
        /** Construct reference and counter from a raw pointer.
         *  @param pt the raw pointer that becomes the managed object.
         */
        Data( T* const pt )
        : m_Count( 1 )
        , m_pT( pt )
#ifdef OF_SHARED_PTR_NEED_MUTEX
        , m_Mutex()
#endif
        {

        }

        /** Delete the managed object.
         *  We are deleted only if the reference counter reached zero,
         *  so we now have to delete the managed object.
         */
        ~Data()
        {
            delete m_pT;
        }

        /** Increment the reference counter in a thread-safe and (if possible)
         *  lock-free fashion.
         */
        void increment_count()
        {
#ifdef HAVE_SYNC_ADD_AND_FETCH
            __sync_add_and_fetch( &m_Count, 1 );
#elif defined HAVE_INTERLOCKED_INCREMENT
            InterlockedIncrement( &m_Count );
#else
            m_Mutex.lock();
            ++m_Count;
            m_Mutex.unlock();
#endif
        }

        /** Decrement the reference counter in a thread-safe and (if possible)
         *  lock-free fashion.
         *  @return OFTrue if the reference counter reached zero, OFFalse otherwise.
         */
        OFBool decrement_count()
        {
#ifdef HAVE_SYNC_SUB_AND_FETCH
            return !__sync_sub_and_fetch( &m_Count, 1 );
#elif defined HAVE_INTERLOCKED_DECREMENT
            return !InterlockedDecrement( &m_Count );
#else
            m_Mutex.lock();
            const OFBool result = !--m_Count;
            m_Mutex.unlock();
            return result;
#endif
        }

        /** Get the managed object.
         *  @return a pointer to the managed object.
         */
        T* pointer() const
        {
            return m_pT;
        }

    private:
        // Just to silence some warnings.
        Data(const Data&);
        Data& operator=(const Data&);

        /// The counter.
        OF_SHARED_PTR_COUNTER_TYPE m_Count;
        /// The pointer to the managed object.
        T* const m_pT;
#ifdef OF_SHARED_PTR_NEED_MUTEX
        /// A mutex for platforms that don't support lock-free counters.
        OFMutex m_Mutex;
#endif
    };

    /** decrement the reference counter, destroy the managed
     *  object and the counter if the counter reached zero.
     */
    void destroy()
    {
        if( m_pData->decrement_count() )
            delete m_pData;
    }

    /** A pointer to the management data structure containing the actual
     *  reference to the managed object and the reference counter.
     */
    Data* m_pData;
};

/** OFunique_ptr is a smart pointer that retains sole ownership of an object through a pointer and destroys
 *  that object when the unique_ptr goes out of scope. No two unique_ptr instances can manage the same object.
 *
 *  The object is destroyed and its memory deallocated when either of the following happens:
 *  - unique_ptr managing the object is destroyed
 *  - unique_ptr managing the object is assigned another pointer via reset().
 *
 *  A unique_ptr may also own no objects, in which case it is called empty.
 *
 *  OFunique_ptr is NOT CopyConstructible or CopyAssignable.
 *  @tparam T the type of the managed object, e.g. int for an OFunique_ptr behaving like an int*.
 *  @note this implementation is meant to be a subset of the c++11's std::unique_ptr that lacks the
 *    following features: swap support, custom deleters, specialized handling of pointers to
 *                        arrays and some functions like comparing OFunique_ptrs or creating a hash key.
 *    see http://en.cppreference.com/w/cpp/memory/unique_ptr to compare OFunique_ptr against std::unique_ptr.
 */
template<typename T>
class OFunique_ptr
{
public:
    /** typedef of pointer type T* for template programmers.
     */
    typedef T* pointer;
    /** T, the type of the object managed by this unique_ptr.
     */
    typedef T element_type;

    /** Constructs an empty OFunique_ptr
     */
    OFunique_ptr() : m_pData( OFnullptr ) {}

    /** Constructs a OFunique_ptr which owns p.
     *  @param p the pointer that's going to be owned by this unique_ptr.
     */
    explicit OFunique_ptr( pointer p ) : m_pData( p ) {}

    /** Move constructor.
     *  The move constructor <i>moves</i> the ownership of the managed
     *  object from its parameter to the newly created OFunique_ptr
     *  object, effectively emptying the former OFunique_ptr
     *  object.
     *  @param rhs an <i>rvalue reference</i> to another OFunique_ptr
     *    object, e.g. obtained via OFmove.
     *  @details
     *  <h3>Example</h3>
     *  This example shows how to move the ownership of a managed
     *  object from one OFunique_ptr to another. Note that the
     *  ownership always remains unique in this example, even
     *  if an error occurred while reading the Dataset.
     *  Therefore no memory leaks are possible!
     *  @code
     *  void consumeDataset( OFunique_ptr<DcmDataset> pDataset )
     *  . . .
     *  OFunique_ptr<DcmDataset> pDataset( new DcmDataset );
     *  if( pDataset && pDataset->read( ... ).good() )
     *  {
     *    // Error: copy constructor not available, as it would
     *    // compromise the unique ownership principle
     *    // consumeDataset( pDataset );
     *
     *    // OFmove allows us to 'tell' OFunique_ptr we want
     *    // to give away the ownership.
     *    consumeDataset( OFmove( pDataset ) );
     *  }
     *  @endcode
     */
    OFunique_ptr( OFrvalue_ref(OFunique_ptr) rhs )
#ifndef DOXYGEN
    : m_pData( rhs.m_pData )
    {
        OFrvalue_access(rhs).m_pData = OFnullptr;
    }
#else
    ;
#endif

    /** Move assignment operator.
     *  The move assignment operator <i>moves</i> the ownership of the
     *  managed object from its parameter to itself, effectively emptying
     *  the other OFunique_ptr object. See OFunique_ptr's <i>move constructor</i>
     *  for more information.
     *  @param rhs an <i>rvalue reference</i> to another OFunique_ptr
     *    object, e.g. obtained via OFmove.
     */
    OFunique_ptr& operator=( OFrvalue_ref(OFunique_ptr) rhs )
#ifndef DOXYGEN
    {
        OFunique_ptr& other = OFrvalue_access(rhs);
        if( &other != this )
        {
            reset( other.m_pData );
            other.m_pData = OFnullptr;
        }
        return *this;
    }
#else
    ;
#endif

    /** If get() == OFnullptr there are no effects -- otherwise, the owned object is destroyed.
     */
    ~OFunique_ptr() { delete m_pData; }

    /** Replaces the managed object. The previously owned object is deleted if the unique_ptr
     *  was not empty.
     *  @param p the new pointer to be owned, defaults to OFnullptr.
     */
    void reset( pointer p = OFnullptr ) { delete m_pData; m_pData = p; }

    /** Releases the ownership of the managed object if any.
     *  Retrieves the owned object (if any) and lets the unique_ptr become empty.
     *  @return same as get().
     */
    pointer release() { pointer const p = m_pData; m_pData = OFnullptr; return p; }

    /** Returns a pointer to the managed object or OFnullptr if no object is owned.
     *  @return Pointer to the managed object or OFnullptr if no object is owned.
     */
    pointer get() const { return m_pData; }

    /** Checks whether *this owns an object, i.e. whether get() != OFnullptr.
     *  @return get() != OFnullptr.
     */
    operator bool() const { return m_pData != OFnullptr; }

    /** Checks whether *this does NOT own an object, i.e. whether get() == OFnullptr.
     *  @return get() == OFnullptr.
     */
    bool operator!() const { return m_pData == OFnullptr; }

    /** Access the object owned by *this.
     *  @return the object owned by *this, i.e. *get().
     */
    T& operator*() const { return *m_pData; }

    /** Access the object owned by *this.
     *  @return same as get().
     */
    pointer operator->() const { return m_pData; }

private:
    /// Disable copy construction.
    OFunique_ptr(const OFunique_ptr&);
    /// Disable copy assignment.
    OFunique_ptr& operator=(const OFunique_ptr&);

    /// The underlying (raw) pointer.
    pointer m_pData;
};

#endif //c++11

#endif //OFMEM_H
