/*
******************************************************************************
* Copyright (C) 2015-2016, International Business Machines
* Corporation and others.  All Rights Reserved.
******************************************************************************
* sharedobject.h
*/

#ifndef __SHAREDOBJECT_H__
#define __SHAREDOBJECT_H__


#include "unicode/uobject.h"
#include "umutex.h"

U_NAMESPACE_BEGIN

/**
 * Base class for unified cache exposing enough methods to SharedObject
 * instances to allow their addRef() and removeRef() methods to
 * update cache metrics. No other part of ICU, except for SharedObject,
 * should directly call the methods of this base class.
 */
class U_COMMON_API UnifiedCacheBase : public UObject {
public:
    UnifiedCacheBase() { }

    /**
     * Called by addRefWhileHoldingCacheLock() when the hard reference count
     * of its instance goes from 0 to 1.
     */
    virtual void incrementItemsInUse() const = 0;

    /**
     * Called by removeRef() when the hard reference count of its instance
     * drops from 1 to 0.
     */
    virtual void decrementItemsInUseWithLockingAndEviction() const = 0;

    /**
     * Called by removeRefWhileHoldingCacheLock() when the hard reference
     * count of its instance drops from 1 to 0.
     */
    virtual void decrementItemsInUse() const = 0;
    virtual ~UnifiedCacheBase();
private:
    UnifiedCacheBase(const UnifiedCacheBase &);
    UnifiedCacheBase &operator=(const UnifiedCacheBase &);
};

/**
 * Base class for shared, reference-counted, auto-deleted objects.
 * Subclasses can be immutable.
 * If they are mutable, then they must implement their copy constructor
 * so that copyOnWrite() works.
 *
 * Either stack-allocate, use LocalPointer, or use addRef()/removeRef().
 * Sharing requires reference-counting.
 */
class U_COMMON_API SharedObject : public UObject {
public:
    /** Initializes totalRefCount, softRefCount to 0. */
    SharedObject() :
            totalRefCount(0),
            softRefCount(0),
            hardRefCount(0),
            cachePtr(NULL) {}

    /** Initializes totalRefCount, softRefCount to 0. */
    SharedObject(const SharedObject &other) :
            UObject(other),
            totalRefCount(0),
            softRefCount(0),
            hardRefCount(0),
            cachePtr(NULL) {}

    virtual ~SharedObject();

    /**
     * Increments the number of references to this object. Thread-safe.
     */
    void addRef() const { addRef(FALSE); }

    /**
     * Increments the number of references to this object.
     * Must be called only from within the internals of UnifiedCache and
     * only while the cache global mutex is held.
     */
    void addRefWhileHoldingCacheLock() const { addRef(TRUE); }

    /**
     * Increments the number of soft references to this object.
     * Must be called only from within the internals of UnifiedCache and
     * only while the cache global mutex is held.
     */
    void addSoftRef() const;

    /**
     * Decrements the number of references to this object. Thread-safe.
     */
    void removeRef() const { removeRef(FALSE); }

    /**
     * Decrements the number of references to this object.
     * Must be called only from within the internals of UnifiedCache and
     * only while the cache global mutex is held.
     */
    void removeRefWhileHoldingCacheLock() const { removeRef(TRUE); }

    /**
     * Decrements the number of soft references to this object.
     * Must be called only from within the internals of UnifiedCache and
     * only while the cache global mutex is held.
     */
    void removeSoftRef() const;

    /**
     * Returns the reference counter including soft references.
     * Uses a memory barrier.
     */
    int32_t getRefCount() const;

    /**
     * Returns the count of soft references only.
     * Must be called only from within the internals of UnifiedCache and
     * only while the cache global mutex is held.
     */
    int32_t getSoftRefCount() const { return softRefCount; }

    /**
     * Returns the count of hard references only. Uses a memory barrier.
     * Used for testing the cache. Regular clients won't need this.
     */
    int32_t getHardRefCount() const;

    /**
     * If noHardReferences() == TRUE then this object has no hard references.
     * Must be called only from within the internals of UnifiedCache.
     */
    inline UBool noHardReferences() const { return getHardRefCount() == 0; }

    /**
     * If hasHardReferences() == TRUE then this object has hard references.
     * Must be called only from within the internals of UnifiedCache.
     */
    inline UBool hasHardReferences() const { return getHardRefCount() != 0; }

    /**
     * If noSoftReferences() == TRUE then this object has no soft references.
     * Must be called only from within the internals of UnifiedCache and
     * only while the cache global mutex is held.
     */
    UBool noSoftReferences() const { return (softRefCount == 0); }

    /**
     * Deletes this object if it has no references or soft references.
     */
    void deleteIfZeroRefCount() const;

    /**
     * @internal For UnifedCache use only to register this object with itself.
     *   Must be called before this object is exposed to multiple threads.
     */
    void registerWithCache(const UnifiedCacheBase *ptr) const {
        cachePtr = ptr;
    }

    /**
     * Returns a writable version of ptr.
     * If there is exactly one owner, then ptr itself is returned as a
     *  non-const pointer.
     * If there are multiple owners, then ptr is replaced with a
     * copy-constructed clone,
     * and that is returned.
     * Returns NULL if cloning failed.
     *
     * T must be a subclass of SharedObject.
     */
    template<typename T>
    static T *copyOnWrite(const T *&ptr) {
        const T *p = ptr;
        if(p->getRefCount() <= 1) { return const_cast<T *>(p); }
        T *p2 = new T(*p);
        if(p2 == NULL) { return NULL; }
        p->removeRef();
        ptr = p2;
        p2->addRef();
        return p2;
    }

    /**
     * Makes dest an owner of the object pointed to by src while adjusting
     * reference counts and deleting the previous object dest pointed to
     * if necessary. Before this call is made, dest must either be NULL or
     * be included in the reference count of the object it points to.
     *
     * T must be a subclass of SharedObject.
     */
    template<typename T>
    static void copyPtr(const T *src, const T *&dest) {
        if(src != dest) {
            if(dest != NULL) { dest->removeRef(); }
            dest = src;
            if(src != NULL) { src->addRef(); }
        }
    }

    /**
     * Equivalent to copyPtr(NULL, dest).
     */
    template<typename T>
    static void clearPtr(const T *&ptr) {
        if (ptr != NULL) {
            ptr->removeRef();
            ptr = NULL;
        }
    }

private:
    mutable u_atomic_int32_t totalRefCount;

    // Any thread modifying softRefCount must hold the global cache mutex
    mutable int32_t softRefCount;

    mutable u_atomic_int32_t hardRefCount;
    mutable const UnifiedCacheBase *cachePtr;
    void addRef(UBool withCacheLock) const;
    void removeRef(UBool withCacheLock) const;

};

U_NAMESPACE_END

#endif
