/*
 * Copyright (c) 2023-2025, Gregory Bertilson <gregory@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/AtomicRefCounted.h>
#include <AK/Function.h>
#include <AK/Time.h>
#include <LibCore/Forward.h>
#include <LibCore/Promise.h>
#include <LibCore/ThreadedPromise.h>
#include <LibMedia/Audio/SampleSpecification.h>
#include <LibMedia/Export.h>

namespace Audio {

enum class OutputState {
    Playing,
    Suspended,
};

// This class implements high-level audio playback behavior. It is primarily intended as an abstract cross-platform
// interface to be used by Ladybird (and its dependent libraries) for playback.
//
// The interface is designed to be simple and robust. All control functions can be called safely from any thread.
// Timing information provided by the class should allow audio timestamps to be tracked with the best accuracy possible.
class MEDIA_API PlaybackStream : public AtomicRefCounted<PlaybackStream> {
public:
    using CreatePromise = Core::Promise<NonnullRefPtr<PlaybackStream>>;
    using AudioDataRequestCallback = Function<ReadonlySpan<float>(Span<float> buffer)>;

    // Begins creating a new audio output and returns a promise that is resolved when it is ready.
    //
    // The initial_output_state parameter determines whether it will begin playback immediately.
    //
    // The returned promise will be resolved with the PlaybackStream if the audio output was successfully initialized,
    // or rejected with an error if not.
    //
    // The AudioDataRequestCallback will be called when the output needs more audio data to fill its buffers and
    // continue playback. This callback will only be allowed to run on one thread at a time, to prevent any data
    // race on the resource used by the callback.
    static NonnullRefPtr<CreatePromise> create(OutputState initial_output_state, u32 target_latency_ms, AudioDataRequestCallback&&);

    virtual SampleSpecification sample_specification() const = 0;

    virtual ~PlaybackStream() = default;

    // Sets the callback function that will be fired whenever the server consumes more data than is made available
    // by the data request callback. It will fire when either the data request runs too long, or the data request
    // returns no data. If all the input data has been exhausted and this event fires, that means that playback
    // has ended.
    virtual void set_underrun_callback(Function<void()>) = 0;

    // Resume playback from the suspended state, requesting new data for audio buffers as soon as possible.
    //
    // The value provided to the promise resolution will match the `total_time_played()` at the exact moment that
    // the stream was resumed.
    virtual NonnullRefPtr<Core::ThreadedPromise<AK::Duration>> resume() = 0;
    // Completes playback of any buffered audio data and then suspends playback and buffering.
    virtual NonnullRefPtr<Core::ThreadedPromise<void>> drain_buffer_and_suspend() = 0;
    // Drops any buffered audio data and then suspends playback and buffering. This can used be to stop playback
    // as soon as possible instead of waiting for remaining audio to play.
    virtual NonnullRefPtr<Core::ThreadedPromise<void>> discard_buffer_and_suspend() = 0;

    // Notifies the stream that the data request callback may be able to provide data now. This is used to
    // wake playback streams that have stopped requesting data due to an underrun.
    virtual void notify_data_available() = 0;

    // Returns a accurate monotonically-increasing time duration that is based on the number of samples that have
    // been played by the output device. The value is interpolated and takes into account latency to the speakers
    // whenever possible.
    //
    // This function should be able to run from any thread safely.
    virtual AK::Duration total_time_played() const = 0;

    virtual NonnullRefPtr<Core::ThreadedPromise<void>> set_volume(double volume) = 0;
};

}
