/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkIDChangeListener_DEFINED
#define SkIDChangeListener_DEFINED

#include "include/core/SkRefCnt.h"
#include "include/private/base/SkMutex.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkThreadAnnotations.h"

#include <atomic>

/**
 * Used to be notified when a gen/unique ID is invalidated, typically to preemptively purge
 * associated items from a cache that are no longer reachable. The listener can
 * be marked for deregistration if the cached item is remove before the listener is
 * triggered. This prevents unbounded listener growth when cache items are routinely
 * removed before the gen ID/unique ID is invalidated.
 */
class SkIDChangeListener : public SkRefCnt {
public:
    SkIDChangeListener();

    ~SkIDChangeListener() override;

    virtual void changed() = 0;

    /**
     * Mark the listener is no longer needed. It should be removed and changed() should not be
     * called.
     */
    void markShouldDeregister() { fShouldDeregister.store(true, std::memory_order_relaxed); }

    /** Indicates whether markShouldDeregister was called. */
    bool shouldDeregister() { return fShouldDeregister.load(std::memory_order_acquire); }

    /** Manages a list of SkIDChangeListeners. */
    class List {
    public:
        List();

        ~List();

        /**
         * Add a new listener to the list. It must not already be deregistered. Also clears out
         * previously deregistered listeners.
         */
        void add(sk_sp<SkIDChangeListener> listener) SK_EXCLUDES(fMutex);

        /**
         * The number of registered listeners (including deregisterd listeners that are yet-to-be
         * removed.
         */
        int count() const SK_EXCLUDES(fMutex);

        /** Calls changed() on all listeners that haven't been deregistered and resets the list. */
        void changed() SK_EXCLUDES(fMutex);

        /** Resets without calling changed() on the listeners. */
        void reset() SK_EXCLUDES(fMutex);

    private:
        mutable SkMutex fMutex;
        skia_private::STArray<1, sk_sp<SkIDChangeListener>> fListeners SK_GUARDED_BY(fMutex);
    };

private:
    std::atomic<bool> fShouldDeregister;
};

#endif
