/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce::midi_ci
{

/**
    A strongly-typed identifier for a 7-bit request ID with a nullable state.

    @tags{Audio}
*/
class RequestID
{
public:
    /** Constructs a RequestID if the provided value is valid, i.e. its most significant bit is
        not set. Otherwise, returns nullopt.
    */
    static std::optional<RequestID> create (uint8_t v)
    {
        if (v < 128)
            return RequestID { v };

        return {};
    }

    /** Constructs a RequestID if the provided value is valid, i.e. its most significant bit is
        not set. Otherwise, returns nullopt.
    */
    static std::optional<RequestID> create (std::byte value)
    {
        return create (static_cast<uint8_t> (value));
    }

    /** Returns the byte corresponding to this ID. */
    std::byte asByte() const
    {
        return std::byte { value };
    }

    /** Returns the int value of this ID. */
    uint8_t asInt() const
    {
        return value;
    }

    /** Equality operator. */
    bool operator== (RequestID other) const
    {
        return value == other.value;
    }

    /** Inequality operator. */
    bool operator!= (RequestID other) const
    {
        return ! operator== (other);
    }

private:
    /*  Constructs a non-null request ID.

        The argument must not have its most significant bit set.
    */
    explicit RequestID (uint8_t index)
        : value (index)
    {
        // IDs must only use the lowest 7 bits
        jassert (value < 128);
    }

    uint8_t value{};
};

/** A strongly-typed 64-bit identifier. */
enum class Token64 : uint64_t {};

/** Compares Token64 instances. */
constexpr bool operator< (Token64 a, Token64 b)
{
    return toUnderlyingType (a) < toUnderlyingType (b);
}

/**
    Accumulates message chunks that have been sent by another device in response
    to a transaction initiated by a local device.

    @tags{Audio}
*/
class InitiatorPropertyExchangeCache
{
public:
    InitiatorPropertyExchangeCache();
    ~InitiatorPropertyExchangeCache();

    InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept;
    InitiatorPropertyExchangeCache& operator= (InitiatorPropertyExchangeCache&&) noexcept;

    JUCE_DECLARE_NON_COPYABLE (InitiatorPropertyExchangeCache)

    /** Picks an unused request ID, and prepares the cache for that ID to accumulate message chunks.

        Incoming chunks added with addChunk are generated by another device acting as a responder.
    */
    std::optional<Token64> primeCache (uint8_t maxSimultaneousRequests,
                                       std::function<void (const PropertyExchangeResult&)> onDone);

    /** Terminates/cancels an ongoing transaction.

        Returns true if the termination had an effect (i.e. the transaction was still ongoing), or
        false otherwise (the transaction already ended or never started).
    */
    bool terminate (Token64);

    /** If there's a transaction ongoing with the given request id, returns the token uniquely
        identifying that transaction, otherwise returns nullopt.
    */
    std::optional<Token64> getTokenForRequestId (RequestID) const;

    /** If the token refers to an ongoing transaction, returns the request id of that transaction.
        Otherwise, returns an invalid request id.
    */
    std::optional<RequestID> getRequestIdForToken (Token64) const;

    /** Adds a message chunk for the provided transaction id. */
    void addChunk (RequestID, const Message::DynamicSizePropertyExchange& chunk);

    /** Updates the transaction state based on the contents of the provided notification. */
    void notify (RequestID, Span<const std::byte> header);

    /** Returns all ongoing transactions. */
    std::vector<Token64> getOngoingTransactions() const;

private:
    class Impl;
    std::unique_ptr<Impl> pimpl;
};

//==============================================================================
/**
    Accumulates message chunks that form a request initiated by a remote device.

    @tags{Audio}
*/
class ResponderPropertyExchangeCache
{
public:
    ResponderPropertyExchangeCache();
    ~ResponderPropertyExchangeCache();

    ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept;
    ResponderPropertyExchangeCache& operator= (ResponderPropertyExchangeCache&&) noexcept;

    JUCE_DECLARE_NON_COPYABLE (ResponderPropertyExchangeCache)

    /** Prepares the cache for the given requestID to accumulate message chunks.

        Incoming chunks added with addChunk are generated by another device acting as an initiator.
    */
    void primeCache (uint8_t maxSimultaneousTransactions,
                     std::function<void (const PropertyExchangeResult&)> onDone,
                     RequestID id);

    /** Adds a message chunk for the provided transaction id. */
    void addChunk (RequestID, const Message::DynamicSizePropertyExchange& chunk);

    /** Updates the transaction state based on the contents of the provided notification. */
    void notify (RequestID, Span<const std::byte> header);

    /** Returns the number of transactions that have been started but not finished. */
    int countOngoingTransactions() const;

private:
    class Impl;
    std::unique_ptr<Impl> pimpl;
};

//==============================================================================
/**
    An interface for objects that provide resources for property exchange
    transactions.

    @tags{Audio}
*/
class CacheProvider
{
public:
    virtual ~CacheProvider() = default;

    /** Returns a set containing all of the MUIDs currently known to the provider. */
    virtual std::set<MUID> getDiscoveredMuids() const = 0;

    /** Returns a property exchange cache for accumulating replies to transactions
        we initiated.
    */
    virtual InitiatorPropertyExchangeCache* getCacheForMuidAsInitiator (MUID m) = 0;

    /** Returns a property exchange cache for accumulating requests initiated
        by other devices.
    */
    virtual ResponderPropertyExchangeCache* getCacheForMuidAsResponder (MUID m) = 0;

    /** Returns the maximum sysex size supported by the device with the
        given MUID.
    */
    virtual int getMaxSysexSizeForMuid (MUID m) const = 0;
};

} // namespace juce::midi_ci
