// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef SRC_DAWN_NATIVE_EVENTMANAGER_H_
#define SRC_DAWN_NATIVE_EVENTMANAGER_H_

#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <variant>
#include <vector>

#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
#include "partition_alloc/pointers/raw_ptr.h"
#include "src/dawn/common/Atomic.h"
#include "src/dawn/common/FutureUtils.h"
#include "src/dawn/common/MutexProtected.h"
#include "src/dawn/common/Ref.h"
#include "src/dawn/common/Time.h"
#include "src/dawn/common/WeakRef.h"
#include "src/dawn/native/Error.h"
#include "src/dawn/native/Forward.h"
#include "src/dawn/native/IntegerTypes.h"
#include "src/utils/non_movable.h"

namespace dawn::native {

struct InstanceDescriptor;
class QueueBase;

// Subcomponent of the Instance which tracks callback events for the Future-based callback
// entrypoints. All events from this instance (regardless of whether from an adapter, device, queue,
// etc.) are tracked here, and used by the instance-wide ProcessEvents and WaitAny entrypoints.
//
// TODO(crbug.com/dawn/2050): Can this eventually replace CallbackTaskManager?
//
// There are various ways to optimize ProcessEvents/WaitAny:
// - TODO(crbug.com/dawn/2059) Spontaneously set events as "early-ready" in other places when we see
//   serials advance, e.g. Submit, or when checking a later wait before an earlier wait.
class EventManager final : NonMovable {
  public:
    class TrackedEvent;

    explicit EventManager(InstanceBase* instance);
    ~EventManager();

    // Track a TrackedEvent and give it a FutureID.
    FutureID TrackEvent(Ref<TrackedEvent>&&);

    // Note that SetFutureReady takes a Ref<TrackedEvent> instead of a TrackedEvent* because TSAN
    // found that this function uses the event after setting it ready meaning another thread can
    // race to complete the event as soon as it's ready, i.e. via a WaitAny call, potentially
    // completing and freeing the event leaving the remainder of this call referencing a freed
    // event.
    void SetFutureReady(Ref<TrackedEvent> event);

    // Returns true if future ProcessEvents is needed.
    bool ProcessPollEvents();
    [[nodiscard]] wgpu::WaitStatus WaitAny(std::span<FutureWaitInfo> infos, Nanoseconds timeout);

  private:
    // Internal waiter class that's signaled when specific events become ready.
    class Waiter;

    using EventMap = absl::flat_hash_map<FutureID, Ref<TrackedEvent>>;
    using SortedEventMap = absl::btree_map<FutureID, Ref<TrackedEvent>>;

    // Raw pointer to the Instance to allow for logging. The Instance owns the EventManager, so a
    // raw pointer here is always safe.
    raw_ptr<const InstanceBase> mInstance;
    std::atomic<FutureID> mNextFutureID = 1;

    struct EventState {
        EventMap events;
        // The Waiter's always live on the stack of a calling thread to WaitAny and are removed as
        // soon as that thread yields from the wait. Therefore the pointers are guaranteed to be
        // valid if they are in the list.
        absl::flat_hash_map<FutureID, std::vector<raw_ptr<Waiter>>> waiters;
    };
    MutexProtected<EventState> mEventState;

    // Records last process event id in order to properly return whether or not there are still
    // events to process when we have re-entrant callbacks.
    std::atomic<FutureID> mLastProcessEventID = 0;
};

struct QueueAndSerial {
    WeakRef<QueueBase> queue;
    std::atomic<ExecutionSerial> completionSerial;

    QueueAndSerial(QueueBase* q, ExecutionSerial serial);

    // Returns the most recently completed serial on |queue|. Otherwise, returns |completionSerial|.
    ExecutionSerial GetCompletedSerial() const;
};

// Base class for the objects that back WGPUFutures. TrackedEvent is responsible for the lifetime
// the callback it contains. If TrackedEvent gets destroyed before it completes, it's responsible
// for cleaning up (by calling the callback with an "Unknown" status).
//
// For Future-based and ProcessEvents-based TrackedEvents, the EventManager will track them for
// completion in WaitAny or ProcessEvents. However, once the Instance has lost all its external
// refs, the user can't call either of those methods anymore, so EventManager will stop holding refs
// to any TrackedEvents. Any which are not ref'd elsewhere (in order to be `Spontaneous`ly
// completed) will be cleaned up at that time.
class EventManager::TrackedEvent : public RefCounted {
  public:
    // Subclasses must implement this to complete the event (if not completed) with
    // EventCompletionType::Shutdown.
    ~TrackedEvent() override;

    // Create and track a TrackedEvent that is already completed.
    // Note: if callbackMode is set to other value, the caller need to make sure to wait on it to
    // avoid leakage.
    static Ref<TrackedEvent> CreateAlreadyCompletedEvent(
        EventManager* eventManager,
        wgpu::CallbackMode callbackMode = wgpu::CallbackMode::AllowSpontaneous);

    Future GetFuture() const;

    bool IsProgressing() const { return mIsProgressing; }

    bool IsReadyToComplete() const;

    QueueAndSerial* GetIfQueueAndSerial();
    const QueueAndSerial* GetIfQueueAndSerial() const;

  protected:
    friend class EventManager;

    // Create an event that can be manually signaled.
    explicit TrackedEvent(wgpu::CallbackMode callbackMode, bool readyToComplete = false);

    // Create a TrackedEvent from a queue completion serial.
    TrackedEvent(wgpu::CallbackMode callbackMode,
                 QueueBase* queue,
                 ExecutionSerial completionSerial);

    // Create a TrackedEvent that is already completed.
    struct Completed {};
    TrackedEvent(wgpu::CallbackMode callbackMode, Completed tag);

    // Some events may be non-progressing, i.e. DeviceLost. We tag these events so that we can
    // correctly return whether there is progressing work when users are polling.
    struct NonProgressing {};
    TrackedEvent(wgpu::CallbackMode callbackMode, NonProgressing tag);

    void EnsureComplete(EventCompletionType);
    virtual void Complete(EventCompletionType) = 0;

    wgpu::CallbackMode mCallbackMode = wgpu::CallbackMode::WaitAnyOnly;
    FutureID mFutureID = kNullFutureID;

  private:
    // The EventManager is the only place that should ever call |SetReadyToComplete| because it is
    // the one responsible for notifying/waking threads up for newly completed events.
    friend class EventManager;
    void SetReadyToComplete();

    const bool mIsProgressing = true;

    // Events may be one of two types:
    // - A boolean which will be updated from our code.
    // - A queue and the ExecutionSerial after which the event will be completed. Note that the
    //   atomic bool above can override the status of the queue and serial, i.e. if the bool is
    //   set to true, the event will be marked as ready to complete regardless of whether the
    //   queue has actually reached the corresponding serial.
    Atomic<bool, std::memory_order_acquire, std::memory_order_release> mIsReadyToComplete{false};

    // Note that currently, the queue ref creates a temporary ref cycle
    // (Queue->Device->Instance->EventManager->TrackedEvent). This is OK because the instance will
    // clear out the EventManager on shutdown.
    // TODO(crbug.com/dawn/2067): This is a bit fragile. Is it possible to remove the ref cycle?
    std::optional<QueueAndSerial> mQueueAndSerial;

    // Flag used to ensure that the callback is only completed once.
    std::once_flag mFlag;
};

}  // namespace dawn::native

#endif  // SRC_DAWN_NATIVE_EVENTMANAGER_H_
