//
//  CPObject.h
//  CPFoundation
//
//  Created by Cesar Parent on 26/06/2015.
//  Copyright (c) 2015 Cesar Parent. All rights reserved.
//

#ifndef __CPFoundation__CPObject__
#define __CPFoundation__CPObject__

#include <atomic>
#include <utility>
//#include "CPObjectDebugger.h"

typedef unsigned long CPIndex;

/*!
 * @interface       CPObjectProtocol
 * @ingroup         Foundation
 * @brief           The base protocol that defines how objects are managed.
 * @details         Objects that implement CPObjectProtocol can be used in a reference-counted
 *                  environment: they keep track of the number of objects pointing to them, and
 *                  are responsible for deleting themselves when this retain count reaches `0`.
 *
 *                  Any object that implement CPObjectProtocol can be managed using Automatic
 *                  Reference Counters (ARC).
 */
class CPObjectProtocol {
public:
    
    virtual ~CPObjectProtocol() {}
    
    /*!
     * @brief       Increments the object's retain count.
     * @details     You retain an object when you take ownership of it: it will be kept in memory
     *              at least until you release it.
     * @warning     In most cases, you should not manually retain Objects whose lifetime is managed
     *              by Automatic Reference Counters (ARC). If you do, you are responsible for
     *              releasing them. Failure to do so would result in a memory leak.
     * @return      The object.
     */
    virtual const CPObjectProtocol*
    retain() const = 0;
    
    /*!
     * @brief       Decrements the object's retain count.
     * @details     You release an object when you don't require its existence anymore. A released
     *              object is considered unsafe, and should not be addressed anymore.
     * @warning     In most cases, You should not manually release an object whose lifetime is
     *              managed by Automatic Reference Counters (ARC). The only case where you have to
     *              do so is when you have retained them previously.
     */
    virtual void
    release() const = 0;
    
    /*!
     * @brief   Returns the object's retain count.
     * @return  The object's retain count.
     */
    virtual CPIndex
    retainCount() const = 0;
    
};


/*!
 * @class           CPObject
 * @ingroup         Foundation
 * @brief           The most basic implementation of CPObjectProtocol. Most classes that can be
 *                  allocated in Heap memory derive from it. CPObject provides the default
 *                  implementation of the reference counting system.
 */
class CPObject : public virtual CPObjectProtocol {
    
public:
    
    /**
     * @name        Creating Objects
     * @{
     */
    
    /*!
     * @brief       Creates a new object.
     * @details     Newly created objects are not owned by default and this have a retain count of
     *              `0`.
     */
    CPObject() : _retainCount(0) {
#ifdef DEBUG
        //CPObjectDebugger::objectWasCreated(this);
#endif
    };
    
    CPObject(const CPObject&) = delete;
    CPObject(const CPObject&&) = delete;
    CPObject&
    operator=(const CPObject&) = delete;
    
    /**
     * @}
     */
    
    /*!
     * @internal
     * @brief   Default destructor.
     */
    virtual ~CPObject() {
#ifdef DEBUG
        //CPObjectDebugger::objectWasDestroyed(this);
#endif
    }
    
    
    // MARK: -
    // MARK: reference counting implementation
    
    /**
     * @name        Managing the Object's lifetime
     * @{
     */
    
    const CPObject*
    retain() const {
        //_retainCount.fetch_add(1);
        _retainCount++;
        return this;
    }
    
    void
    release() const {
        if (--_retainCount <= 0) {
            delete this;
        }
    }
    
    CPIndex
    retainCount() const {
        return _retainCount.load();
    }
    
    /**
     * @}
     */
    
private:
    
    mutable std::atomic<CPIndex> _retainCount;
};

/*!
 * @class           ARC
 * @ingroup         Foundation
 * @brief           Automatic Reference Counters are used to automatically manage the lifetime
 *                  of objects that implement CPObjectProtocol.
 *
 * @details         A Counter is allocted on the stack, and holds a reference to the managed
 *                  object. C++'s scope rules ensure that the managed object will be retained
 *                  as long as the Counter is accessible. When the Counter is destroyed, for
 *                  example by exiting a function, it releases its managed object.
 *
 *                  When using ARC pointers, objects *should not* be retained or released manually.
 *                  Doing so would result in either references to destroyed objects or leaking
 *                  objects with higher-than-required retain counts.
 */
template <typename T1>
class ARC {
public:
    
    /*!
     * @brief   Creates a Null-referencing ARC pointer.
     */
    ARC() {
        _ref = nullptr;
    }
    
    /*!
     * @brief   Creates a ARC pointer to an existing object.
     * @param   ref         Pointer to the object to manage.
     */
    explicit ARC(T1* ref) {
        _ref = ref;
        __retain();
    }
    
    /*!
     * @internal
     * @brief   Creates a reference counter from another one. This raises the retain count of the
     *          object it points to.
     * @param   rhs         ARC to copy.
     */
    ARC(const ARC& rhs) {
        _ref = rhs._ref;
        __retain();
    }
    
    /*!
     * @internal
     * @brief   Destructing an Automatic Reference Counter releases the object it points to.
     */
    virtual ~ARC() {
        __release();
    }
    
    /*!
     * @internal
     * @brief   Assigning an ARC to another one raises the retain count of the object it points to 
     *          by one.
     */
    ARC&
    operator=(const ARC& rhs) {
        if(this != &rhs) {
            __release();
            _ref = rhs._ref;
            __retain();
        }
        return *this;
    }
    
    /*!
     * @internal
     * @brief       pointer cast operator
     */
    template<typename T2>
    operator ARC<T2>() {
        return ARC<T2>((T2*)_ref);
    }
    
    /*!
     * @internal
     * @brief       pointer bool cast
     */
    operator bool() const {
        return _ref != nullptr;
    }
    
    T1*
    operator->() const {
        return _ref;
    }
    
    /*!
     * @brief       Returns a non-managed pointer to the object pointed to.
     * @return      The ARC's underlying pointer.
     */
    T1*
    raw() const { return _ref; }
    
private:
    
    void __retain() const {
        if(_ref != nullptr) {
            _ref->retain();
        }
    }
    
    void __release() const {
        if(_ref != nullptr) {
            _ref->release();
        }
    }
    
    T1*     _ref;
};

/*!
 * @internal
 * @brief       Creates an object and wraps it in an Automatic Reference Counter.
 * @ingroup     Foundation
 */
template <typename T, class Arg> ARC<T>
alloc(Arg&& arg) {
    return ARC<T>(new T(std::forward<Arg>(arg)));
}

/*!
 * @brief       Creates an object and wraps it in an Automatic Reference Counter.
 * @ingroup     Foundation
 */
template <typename T, class... Args> ARC<T>
alloc(Args&&... args) {
    return ARC<T>(new T(std::forward<Args>(args)...));
}

#endif /* defined(__CPFoundation__CPObject__) */
