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

   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.

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

#pragma once

namespace juce
{

class AudioProcessor;

/* All these readers follow a common pattern of "invalidation":

   Whenever the samples they are reading are altered, the readers become invalid and will stop
   accessing the model graph. These alterations are model edits such as property changes, content
   changes (if affecting sample scope), or the deletion of some model object involved in the read
   process. Since these edits are performed on the document controller thread, reader validity can
   immediately be checked after the edit has been concluded, and any reader that has become invalid
   can be recreated.

   Note that encountering a failure in any individual read call does not invalidate the reader, so
   that the entity using the reader can decide whether to retry or to back out. This includes trying
   to read an audio source for which the host has currently disabled access: the failure will be
   immediately visible, but the reader will remain valid. This ensures that for example a realtime
   renderer can just keep reading and will be seeing proper samples again once sample access is
   re-enabled.

   If desired, the code calling readSamples() can also implement proper signaling of any read error
   to the document controller thread to trigger rebuilding the reader as needed. This will typically
   be done when implementing audio source analysis: if there is an error upon reading the samples
   that cannot be resolved within a reasonable timeout, then the analysis would be aborted. The
   document controller code that monitors the analysis tasks can evaluate this and re-launch a new
   analysis when appropriate (e.g. when access is re-enabled).

   When reading playback regions (directly or through a region sequence reader), the reader will
   represent the regions as a single source object that covers the union of all affected regions.
   The first sample produced by the reader thus will be the first sample of the earliest region.
   This means that the location of this region has to be taken into account by the calling code if
   it wants to relate the samples to the model or any other reader output.
*/

//==============================================================================
/**
    Subclass of AudioFormatReader that reads samples from a single ARA audio source.

    Plug-Ins typically use this from their rendering code, wrapped in a BufferingAudioReader
    to bridge between realtime rendering and non-realtime audio reading.

    The reader becomes invalidated if
        - the audio source content is updated in a way that affects its samples,
        - the audio source sample access is disabled, or
        - the audio source being read is destroyed.

    @tags{ARA}
*/
class JUCE_API  ARAAudioSourceReader  : public AudioFormatReader,
                                        private ARAAudioSource::Listener
{
public:
    /** Use an ARAAudioSource to construct an audio source reader for the given \p audioSource. */
    explicit ARAAudioSourceReader (ARAAudioSource* audioSource);

    ~ARAAudioSourceReader() override;

    bool readSamples (int* const* destSamples,
                      int numDestChannels,
                      int startOffsetInDestBuffer,
                      int64 startSampleInFile,
                      int numSamples) override;

    /** Returns true as long as the reader's underlying ARAAudioSource remains accessible and its
        sample content is not changed.
    */
    bool isValid() const { return audioSourceBeingRead != nullptr; }

    /** Invalidate the reader - the reader will call this internally if needed, but can also be
        invalidated from the outside (from message thread only!).
    */
    void invalidate();

    void willUpdateAudioSourceProperties (ARAAudioSource* audioSource,
                                          ARAAudioSource::PropertiesPtr newProperties) override;
    void doUpdateAudioSourceContent (ARAAudioSource* audioSource,
                                     ARAContentUpdateScopes scopeFlags) override;
    void willEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) override;
    void didEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) override;
    void willDestroyAudioSource (ARAAudioSource* audioSource) override;

private:
    ARAAudioSource* audioSourceBeingRead;
    std::unique_ptr<ARA::PlugIn::HostAudioReader> hostReader;
    ReadWriteLock lock;
    std::vector<void*> tmpPtrs;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAAudioSourceReader)
};

//==============================================================================
/**
    Subclass of AudioFormatReader that reads samples from a group of playback regions.

    Plug-Ins typically use this to draw the output of a playback region in their UI.

    In order to read from playback regions, the reader requires an audio processor that acts as ARA
    playback renderer. Configuring the audio processor for real-time operation results in the reader
    being real-time capable too, unlike most other AudioFormatReaders. The reader instance will take
    care of adding all regions being read to the renderer and invoke its processBlock function in
    order to read the region samples.

    The reader becomes invalid if
        - any region properties are updated in a way that would affect its samples,
        - any region content is updated in a way that would affect its samples, or
        - any of its regions are destroyed.

    @tags{ARA}
*/
class JUCE_API  ARAPlaybackRegionReader   : public AudioFormatReader,
                                            private ARAPlaybackRegion::Listener
{
public:
    /** Create an ARAPlaybackRegionReader instance to read the given \p playbackRegion, using the
        sample rate and channel count of the underlying ARAAudioSource.

        @param playbackRegion The playback region that should be read - must not be nullptr!
    */
    explicit ARAPlaybackRegionReader (ARAPlaybackRegion* playbackRegion);

    /** Create an ARAPlaybackRegionReader instance to read the given \p playbackRegions

        @param sampleRate      The sample rate that should be used for reading.
        @param numChannels     The channel count that should be used for reading.
        @param playbackRegions The vector of playback regions that should be read - must not be empty!
                               All regions must be part of the same ARADocument.
    */
    ARAPlaybackRegionReader (double sampleRate, int numChannels,
                             const std::vector<ARAPlaybackRegion*>& playbackRegions);

    ~ARAPlaybackRegionReader() override;

    /** Returns true as long as any of the reader's underlying playback region's haven't changed. */
    bool isValid() const { return (playbackRenderer != nullptr); }

    /** Invalidate the reader - this should be called if the sample content of any of the reader's
        ARAPlaybackRegions changes.
    */
    void invalidate();

    bool readSamples (int* const* destSamples,
                      int numDestChannels,
                      int startOffsetInDestBuffer,
                      int64 startSampleInFile,
                      int numSamples) override;

    void willUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion,
                                             ARAPlaybackRegion::PropertiesPtr newProperties) override;
    void didUpdatePlaybackRegionContent (ARAPlaybackRegion* playbackRegion,
                                         ARAContentUpdateScopes scopeFlags) override;
    void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) override;

    /** The starting point of the reader in playback samples */
    int64 startInSamples = 0;

private:
    std::unique_ptr<ARAPlaybackRenderer> playbackRenderer;
    AudioPlayHead::PositionInfo positionInfo;
    ReadWriteLock lock;

    static constexpr int maximumBlockSize = 4 * 1024;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAPlaybackRegionReader)
};

} // namespace juce
