/*  *********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  Astris.framework - C++ API
 *
 *  File: NewAPI_Pointers.hpp
 *
 ***********************************************************************
 *
 *  Copyright (C) 2021- Apple Inc. All rights reserved.
 *
 *  This document is the property of Apple Inc.
 *  It is considered confidential and proprietary.
 *
 *  This document may not be reproduced or transmitted in any form,
 *  in whole or in part, without the express written permission of
 *  Apple Inc.
 ********************************************************************* */

#ifndef NewAPI_Pointers_h
#define NewAPI_Pointers_h

#include "astris_api.h"

#include <utility>

namespace AstrisAPI {

namespace impl {
template <typename T> class InterfaceImpl;
class Notification;
}

/*!
 @class AstrisAPI::ObjectBase
 @discussion Abstract C++ interface base class for all Astris API interface implementations.
 It defines set of shared semantics all API interface objects must support. The shared semantics
 fall into two categories:
 - reference counting for invasive smart pointer support;
 - object hierarchy management.
 @link AstrisAPI::RetainablePtr@/link
 */
class ASTRIS_EXPORT ObjectBase {
public:
    ObjectBase();
    virtual ~ObjectBase();

    virtual int retain() = 0;
    virtual int release() = 0;
    
protected:
    virtual const ObjectBase* parent() const = 0;
    virtual void disown(const ObjectBase *parent) = 0;
    virtual void removeChild(ObjectBase *child) = 0;
    virtual void addChild(ObjectBase *child) = 0;
    virtual void notifyChildren(const ObjectBase *parent, const impl::Notification &message) = 0;
    
private:
    ObjectBase(const ObjectBase&) = delete;
    ObjectBase& operator=(const ObjectBase&) = delete;
    
    template <typename T>
    friend class impl::InterfaceImpl;
};

/*!
 @class AstrisAPI::RetainablePtr
 @discussion Helper class for managing lifetime of invasive ref counting for classes
 that implement retain() and release() methods.
 This helper manages lifetime of underlying object via construction/destruction and
 copy/move operations.
 Typename T is a class that implements retain() and release() virtual methods.
 */
template <typename T>
class RetainablePtr
{
public:
    /*!
    Default constructor, initializes smart pointer with nullptr (no underlying object).
     */
    RetainablePtr():
        m_ptr(nullptr)
    {}
    
    /*!
     Constructs an instance of helper while taking ownership of an object. The object is
     expected to have a valid refcount, for example, 1 right after creation.
     @param ptr a pointer to object of type castable to T.
     */
    template <typename P>
    explicit RetainablePtr(P *ptr):
        m_ptr(static_cast<T*>(ptr))
    {}
    
    /*!
     Copy constructor - creates a new copy of a helper instance from exisitng
     helpere instance and increments reference count of the underlying object
     to reflect a new referece.
     */
    RetainablePtr(const RetainablePtr<T> &other):
        m_ptr(other.get())
    {
        retain();
    }
    
    /*!
     Copy construcor for compatible helper instance. Similar to regular copy
     constructor, but allows the other helper to be of castable type.
     */
    template <typename P>
    RetainablePtr(const RetainablePtr<P> &other):
        m_ptr(nullptr)
    {
        m_ptr = static_cast<T*>(other.get());
        retain();
    }
    
    /*!
     Move constructor - takes over the management of underlying object from
     another instance of the helper. The count of references to underlying object,
     does not change, because the other helper relinquishes its reference.
     */
    RetainablePtr(RetainablePtr<T> &&other):
        m_ptr(nullptr)
    {
        swap(::std::forward<RetainablePtr<T>>(other));
    }
    
    /*!
     Destructor - decrements underlying object's ref count, since the reference of this
     helper is going out of scope.
     */
    ~RetainablePtr()
    {
        release();
    }
    
    /*!
     Copy assignment operator - similar to copy constructor, except this helper might
     already be managing underlying object, hence it releases the current reference, if any
     */
    RetainablePtr& operator=(const RetainablePtr<T> &other)
    {
        release();
        m_ptr = other.get();
        retain();
        return *this;
    }
    
    /*!
     Copy assignment operator for compatible helper.
     */
    template <typename P>
    RetainablePtr& operator=(const RetainablePtr<P> &other)
    {
        release();
        m_ptr = static_cast<T*>(other.get());
        retain();
        return *this;
    }
    
    /*!
     Move assignment operator - takes over management of underlying object from
     another instance of helper.
     */
    RetainablePtr& operator=(RetainablePtr &&other)
    {
        release();
        m_ptr = nullptr;
        swap(::std::forward<RetainablePtr<T>>(other));
        return *this;
    }

    /*!
     Specialization of assignment operator for nullptr. Effectively relinquishes
     management of the underlying object.
     */
    RetainablePtr& operator=(std::nullptr_t)
    {
        release();
        m_ptr = nullptr;
        return *this;
    }
    
    /*!
     Swaps ownership of underlying objects between two helper instances. The refcount
     of underlying objects does not change, just the management relationship.
     */
    void swap(RetainablePtr &&other)
    {
        T *other_copy = other.get();
        other.m_ptr = m_ptr;
        m_ptr = other_copy;
    }

    // Operators for approximating pointer semantics
    operator bool() const { return m_ptr != nullptr; }
    T& operator*() const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
    T* get() const { return m_ptr; }
    
protected:

    void release()
    {
        if (m_ptr != nullptr) {
            m_ptr->release();
        }
        m_ptr = nullptr;
    }

    void retain()
    {
        if (m_ptr != nullptr) {
            m_ptr->retain();
        }
    }
    
private:
    T *m_ptr;
};

/*!
 Comparison operator for compatible smart pointer helpers to allow comparing
 underlying manage object pointers of two, possibly different, but compatible types.
 */
template <typename T, typename P>
bool operator==(const RetainablePtr<T> &that, const RetainablePtr<P> &other)
{
    return that.get() == static_cast<T*>(other.get());
}

}   // namespace AstrisAPI

#endif /* NewAPI_Pointers_h */
