/*
 *   ORXONOX - the hottest 3D action shooter ever to exist
 *                    > www.orxonox.net <
 *
 *
 *   License notice:
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation; either version 2
 *   of the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 *   Author:
 *      Fabian 'x3n' Landau
 *   Co-authors:
 *      ...
 *
 */

// Inspired by boost::intrusive_ptr by Peter Dimov

/**
    @defgroup SmartPtr StrongPtr<T> and WeakPtr<T>
    @ingroup Object
*/

/**
    @file
    @ingroup Object SmartPtr
    @brief Definition of StrongPtr<T>, wraps a pointer to an object and keeps it alive.

    @anchor StrongPtrExample

    orxonox::StrongPtr is an implementation of a smart pointer - it wraps a pointer to an
    object  and keeps this object alive until no StrongPtr points to this object anymore.
    In contrast to std::shared_ptr, StrongPtr works only with classes that are derived
    from orxonox::Destroyable, because it's an intrusive implementation, meaning the
    reference counter is stored in the object itself.

    It's possible to use normal pointers and strong pointers to an object simultaneously.
    You don't have to use StrongPtr all the time, you can create a StrongPtr for an object
    at any time and also convert it back to a normal pointer if you like. This is possible
    because the reference counter is stored in the object itself and not in StrongPtr (in
    contrast to std::shared_ptr).

    @b Important: If you want to delete an object, you must not use @c delete @c object but
    rather @c object->destroy(). This function will check if there are strong pointers
    pointing to the object. If yes, the object will be kept alive until all strong pointers
    are destroyed. If no, the object is deleted instantly.

    If all strong pointers that point to an object are destroyed, but you never called
    @c object->destroy() before, the object will not be deleted! All a StrongPtr will do
    is to really just keep an object alive, but it will not delete it automatically
    unless you tried to destroy it before.

    Example:
    @code
    class MyClass                                           // class declaration
    {
        public:
            void setObject(OtherClass* object)              // passes a normal pointer which will be stored in a StrongPtr
                { this->object_ = object; }

            OtherClass* getObject() const                   // converts the StrongPtr to a normal pointer and returns it
                { return this->object_; }

        private:
            StrongPtr<OtherClass> object_;                  // a pointer to an instance of OtherClass is stored in a StrongPtr
    };
    @endcode
    In this example we assume that OtherClass is a child of Destroyable. We don't care
    about the inheritance of MyClass though.

    Now we create an instance of MyClass and assign a pointer to an instance of OtherClass:
    @code
    MyClass* myclass = new MyClass();                       // create an instance of MyClass
    OtherClass* object = new OtherClass();                  // create an instance of OtherClass
    myclass->setObject(object);                             // the object is now stored in a StrongPtr inside myclass

    object->destroy();                                      // we try to destroy object, but there's still a StrongPtr pointing at it.

    # object still exists at this point (because a StrongPtr points at it)

    delete myclass;                                         // now we delete myclass, which also destroys the StrongPtr

    # object doesn't exist anymore (because the StrongPtr is now destroyed)
    @endcode

    Now we look at the same example, but we first delete myclass, then destroy object:
    @code
    MyClass* myclass = new MyClass();                       // create an instance of MyClass
    OtherClass* object = new OtherClass();                  // create an instance of OtherClass
    myclass->setObject(object);                             // the object is now stored in a StrongPtr inside myclass

    delete myclass;                                         // we delete myclass, which also destroys the StrongPtr

    # object still exists at this point (because destroy() was not called yet)

    object->destroy();                                      // now we try to destroy object, which works instantly

    # object doesn't exist anymore (because we just destroyed it)
    @endcode

    Note that in any case @c object->destroy() has to be called to delete the object.
    However if a StrongPtr points at it, the destruction is delayed until all StrongPtr
    are destroyed.
*/

#ifndef _StrongPtr_H__
#define _StrongPtr_H__

#include "core/CorePrereqs.h"

#include <cassert>

#include "core/object/Destroyable.h"
#include "WeakPtr.h"

namespace orxonox
{
    /**
        @brief A strong pointer which wraps a pointer to an object and keeps this object alive as long as the strong pointer exists.

        @see See @ref StrongPtrExample "this description" for more information and an example.
    */
    template <class T>
    class StrongPtr
    {
        public:
            /// Constructor: Initializes the strong pointer with a null pointer.
            inline StrongPtr() : pointer_(nullptr), base_(nullptr)
            {
            }

            /// Constructor: Initializes the strong pointer with a pointer to an object. @param pointer The pointer
            inline StrongPtr(T* pointer) : pointer_(pointer), base_(pointer)
            {
                if (this->base_)
                    this->base_->incrementReferenceCount();
            }

            /// Copy-constructor
            inline StrongPtr(const StrongPtr& other) : pointer_(other.pointer_), base_(other.base_)
            {
                if (this->base_)
                    this->base_->incrementReferenceCount();
            }

            /// Copy-constructor for strong pointers to objects of another class.
            template <class O>
            inline StrongPtr(const StrongPtr<O>& other) : pointer_(other.get()), base_(other.getBase())
            {
                if (this->base_)
                    this->base_->incrementReferenceCount();
            }

            /// Constructor: Initializes the strong pointer with the pointer that is stored in a WeakPtr.
            template <class O>
            inline StrongPtr(const WeakPtr<O>& other) : pointer_(other.get()), base_(other.getBase())
            {
                if (this->base_)
                    this->base_->incrementReferenceCount();
            }

            /// Move-constructor
            inline StrongPtr(StrongPtr&& other) : pointer_(other.pointer_), base_(other.base_)
            {
                other.pointer_ = nullptr;
                other.base_ = nullptr;
            }

            /// Destructor: Decrements the reference counter.
            inline ~StrongPtr()
            {
                if (this->base_)
                    this->base_->decrementReferenceCount();
            }

            /// Assigns a new pointer.
            inline StrongPtr& operator=(T* pointer)
            {
                StrongPtr(pointer).swap(*this);
                return *this;
            }

            /// Assigns the wrapped pointer of another StrongPtr.
            inline StrongPtr& operator=(StrongPtr other)
            {
                other.swap(*this);
                return *this;
            }

            /// Assigns the wrapped pointer of a StrongPtr of another class
            template <class O>
            inline StrongPtr& operator=(const StrongPtr<O>& other)
            {
                StrongPtr(other).swap(*this);
                return *this;
            }

            /// Assigns the wrapped pointer of a WeakPtr.
            template <class O>
            inline StrongPtr& operator=(const WeakPtr<O>& other)
            {
                StrongPtr(other).swap(*this);
                return *this;
            }

            /// Returns the wrapped pointer as @c T*
            inline T* get() const
            {
                return this->pointer_;
            }

            /// Returns the wrapped pointer as @c Destroyable*
            inline Destroyable* getBase() const
            {
                return this->base_;
            }

            /// Implicitly converts the StrongPtr to a pointer of type @c T*
            inline operator T*() const
            {
                return this->pointer_;
            }

            /// Overloaded operator, returns a pointer to the stored object.
            inline T* operator->() const
            {
                assert(this->pointer_ != nullptr);
                return this->pointer_;
            }

            /// Overloaded operator, returns a reference to the stored object.
            inline T& operator*() const
            {
                assert(this->pointer_ != nullptr);
                return *this->pointer_;
            }

            /// Returns true if the pointer is not nullptr.
            inline explicit operator bool() const
            {
                return (this->pointer_ != nullptr);
            }

            /// Swaps the contents of two strong pointers.
            inline void swap(StrongPtr& other)
            {
                {
                    T* temp = this->pointer_;
                    this->pointer_ = other.pointer_;
                    other.pointer_ = temp;
                }
                {
                    Destroyable* temp = this->base_;
                    this->base_ = other.base_;
                    other.base_ = temp;
                }
            }

            /// Resets the strong pointer (equivalent to assigning a nullptr).
            inline void reset()
            {
                StrongPtr().swap(*this);
            }

        private:
            T* pointer_;            ///< The wrapped pointer to an object of type @a T
            Destroyable* base_;    ///< The wrapped pointer, casted up to Destroyable (this is needed because with just a T* pointer, StrongPtr couln't be used with forward declarations)
    };

    /// Swaps the contents of two strong pointers.
    template <class T>
    void swap(StrongPtr<T>& a, StrongPtr<T>& b)
    {
        a.swap(b);
    }

    /// Uses a static_cast to cast a pointer of type U* to a pointer of type T* and returns it in a new StrongPtr<T>.
    template <class T, class U>
    StrongPtr<T> static_pointer_cast(const StrongPtr<U>& p)
    {
        return static_cast<T*>(p.get());
    }

    /// Uses a const_cast to cast a pointer of type U* to a pointer of type T* and returns it in a new StrongPtr<T>.
    template <class T, class U>
    StrongPtr<T> const_pointer_cast(const StrongPtr<U>& p)
    {
        return const_cast<T*>(p.get());
    }

    /// Uses a dynamic_cast to cast a pointer of type U* to a pointer of type T* and returns it in a new StrongPtr<T>.
    template <class T, class U>
    StrongPtr<T> dynamic_pointer_cast(const StrongPtr<U>& p)
    {
        return orxonox_cast<T*>(p.get());
    }
}

#endif /* _StrongPtr_H__ */
