/**
 * osgEarth
 * Copyright Pelican Mapping
 * MIT License
 */
#pragma once

#include <atomic>
#include <mutex>
#include <functional>
#include <vector>
#include <osg/observer_ptr>

namespace osgEarth
{
    /**
     * Easy way to add a thread-safe callback to a class.
     *
     * Developer defines a callback, usually as a class member:
     *   Callback<void(int)> onClick;
     *
     * User adds a callback:
     *   uid = instance->onClick([](int a) { ... });
     *
     * Class fires a callback:
     *   onClick.fire(42);
     *
     * User removes a callback:
     *   instance->onClick.remove(uid);
     */
    template<typename F>
    class Callback
    {
    private:
        using Entry = typename std::pair<int, std::function<F>>;
        mutable int uidgen = 0;
        mutable std::vector<Entry> entries;
        mutable std::mutex mutex;
        mutable std::atomic_bool firing = { false };

    public:
        //! Adds a callback function (by stealable rvalue)
        int operator()(std::function<F>&& func) const {
            std::lock_guard<std::mutex> lock(mutex);
            auto uid = ++uidgen;
            entries.emplace_back(uid, std::move(func));
            return uid;
        }

        //! Adds a callback function
        int operator()(std::function<F>& func) const {
            std::lock_guard<std::mutex> lock(mutex);
            auto uid = ++uidgen;
            entries.emplace_back(uid, func);
            return uid;
        }

        //! Removed a callback function with the UID returned from ()
        void remove(int uid) const {
            std::lock_guard<std::mutex> lock(mutex);
            for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
                if (iter->first == uid) {
                    entries.erase(iter);
                    break;
                }
            }
        }

        //! Executes all callback functions with the provided args
        template<typename... Args>
        void fire(Args&&... args) const {
            if (firing.exchange(true) == false) {
                std::lock_guard<std::mutex> lock(mutex);
                for (auto& e : entries)
                    e.second(args...);
                firing = false;
            }
        }

        //! Whether there are any callbacks registered
        operator bool() const {
            std::lock_guard<std::mutex> lock(mutex);
            return !entries.empty();
        }
    };
}