// Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

//! @file
//!
//! @brief Type definitions for *carb.eventdispatcher.plugin*
#pragma once

#include "../Strong.h"
#include "../variant/VariantUtils.h"

namespace carb
{
namespace eventdispatcher
{

//! A handle to an observer, registered with \ref IEventDispatcher::observeEvent().
CARB_STRONGTYPE(Observer, size_t);

//! Special value indicating an invalid observer.
constexpr Observer kInvalidObserver{};

//! Structure definition for a named variant: a key/value pair with a value of varying type.
struct NamedVariant
{
    RStringKey name; //!< The key
    variant::Variant value; //!< The value
};
static_assert(std::is_standard_layout<NamedVariant>::value, ""); // Not interop-safe as it is not trivially copyable

//! Structure definition for event data. Typically not used; instead use the \ref Event wrapper-class.
struct EventData
{
    RString eventName; //!< The name of the event
    size_t numVariants; //!< Number of items in the \c variants array member
    NamedVariant const* variants; //!< An array of \ref NamedVariant of length \c numVariants
};
CARB_ASSERT_INTEROP_SAFE(EventData);

//! A presentation class for \ref EventData. Allows querying the data via a simple C++ interface.
class Event final : public EventData
{
    CARB_PREVENT_COPY_AND_MOVE(Event);

public:
    /**
     * Returns true if an Event contains the requested key.
     *
     * @param key The key to query.
     * @returns \c true if the key is contained in the Event, \c false otherwise.
     */
    bool hasKey(RStringKey key) const;

    /**
     * Returns a variant value from the event by key.
     * @param key The key to query.
     * @returns A \ref variant::Variant representing the item if the key is present; \c nullptr otherwise.
     */
    const variant::Variant* get(RStringKey key) const;

    /**
     * Retrieves a value from the Event by key.
     *
     * @tparam T The requested type to convert the value to.
     * @param key The key to query.
     * @returns An \c optional value where \c has_value() is \c true if the key exists and the variant value that it
     * represents can be converted to type \c T. Otherwise returns an empty \c optional.
     */
    template <class T>
    cpp::optional<T> getValue(RStringKey key) const;

    /**
     * Retrieves a value from the Event by key, or a fallback value.
     *
     * Effectively the same as `getValue<T>(key).value_or(defaultValue)`
     *
     * @tparam T The requested type to convert the value to.
     * @param key The key to query.
     * @param defaultValue A value to return if the key does not exist or is not convertible to \c T.
     */
    template <class T>
    T getValueOr(RStringKey key, T&& defaultValue) const;
};
static_assert(sizeof(Event) == sizeof(EventData), "");

//! Callback function called when an event is dispatched. Typically not manually used as it is generated by
//! \ref IEventDispatcher::observeEvent().
using ObserverFn = void (*)(const Event&, void*);

//! Callback function called when an observer is terminated with \ref IEventDispatcher::stopObserving(). Typically not
//! manually used as it is generated by \ref IEventDispatcher::observeEvent().
using CleanupFn = void (*)(void*);

//! Structure to manage the lifetime of an observer. Similar to \c std::unique_ptr.
class ObserverGuard
{
public:
    CARB_PREVENT_COPY(ObserverGuard);

    /**
     * Default constructor. Constructs an empty \c ObserverGuard.
     */
    constexpr ObserverGuard() noexcept;

    /**
     * Constructor that accepts an \ref Observer handle.
     * @param o The \ref Observer to manage.
     */
    constexpr explicit ObserverGuard(Observer o) noexcept;

    /**
     * Move constructor. Moves the \ref Observer managed by \p other to \c *this and leaves \p other empty.
     * @param other The other \c ObserverGuard.
     */
    ObserverGuard(ObserverGuard&& other) noexcept;

    /**
     * Destructor.
     */
    ~ObserverGuard() noexcept;

    /**
     * Move-assign operator. Moves the \ref Observer managed by \p other to \c *this.
     * @param other The other \c ObserverGuard.
     */
    ObserverGuard& operator=(ObserverGuard&& other) noexcept;

    /**
     * Releases the managed \ref Observer to the caller and leaves \c *this empty.
     *
     * @note This does not stop the managed \ref Observer.
     * @return The \ref Observer previously managed by \c *this.
     */
    Observer release() noexcept;

    /**
     * Stops any currently managed observer (as via \ref IEventDispatcher::stopObserving()) and takes ownership of the
     * given \ref Observer.
     *
     * @param o The new \ref Observer to manage, or \ref kInvalidObserver to remain empty.
     */
    void reset(Observer o = kInvalidObserver) noexcept;

    /**
     * Exchanges state with another \c ObserverGuard.
     * @param o The other \c ObserverGuard.
     */
    void swap(ObserverGuard& o) noexcept;

    /**
     * Returns the managed \ref Observer while maintaining management of it.
     * @returns The managed \ref Observer.
     */
    constexpr Observer get() const noexcept;

    /**
     * Validation test.
     * @returns \c true if \c *this is non-empty (manages a valid observer); \c false otherwise.
     */
    explicit operator bool() const noexcept;

private:
    Observer m_o;
};

} // namespace eventdispatcher
} // namespace carb
