// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <span>
#include <vector>

#include "audio_core/common/common.h"
#include "common/common_types.h"
#include "common/polyfill_thread.h"
#include "common/reader_writer_queue.h"
#include "common/ring_buffer.h"
#include "common/thread.h"

namespace Core {
class System;
} // namespace Core

namespace AudioCore::Sink {

enum class StreamType {
    Render,
    Out,
    In,
};

struct SinkBuffer {
    u64 frames;
    u64 frames_played;
    u64 tag;
    bool consumed;
};

/**
 * Contains a real backend stream for outputting samples to hardware,
 * created only via a Sink (See Sink::AcquireSinkStream).
 *
 * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer).
 * Appended buffers act as a FIFO queue, and will be held until played.
 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
 * has been consumed.
 *
 * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
 * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
 * never release.
 *
 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
 */
class SinkStream {
public:
    explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
    virtual ~SinkStream() {}

    /**
     * Finalize the sink stream.
     */
    virtual void Finalize() {}

    /**
     * Start the sink stream.
     *
     * @param resume - Set to true if this is resuming the stream a previously-active stream.
     *                 Default false.
     */
    virtual void Start(bool resume = false) {}

    /**
     * Stop the sink stream.
     */
    virtual void Stop() {}

    /**
     * Check if the stream is paused.
     *
     * @return True if paused, otherwise false.
     */
    bool IsPaused() const {
        return paused;
    }

    /**
     * Get the number of system channels in this stream.
     *
     * @return Number of system channels.
     */
    u32 GetSystemChannels() const {
        return system_channels;
    }

    /**
     * Set the number of channels the system expects.
     *
     * @param channels - New number of system channels.
     */
    void SetSystemChannels(u32 channels) {
        system_channels = channels;
    }

    /**
     * Get the number of channels the hardware supports.
     *
     * @return Number of channels supported.
     */
    u32 GetDeviceChannels() const {
        return device_channels;
    }

    /**
     * Get the system volume.
     *
     * @return The current system volume.
     */
    f32 GetSystemVolume() const {
        return system_volume;
    }

    /**
     * Get the device volume.
     *
     * @return The current device volume.
     */
    f32 GetDeviceVolume() const {
        return device_volume;
    }

    /**
     * Set the system volume.
     *
     * @param volume_ - The new system volume.
     */
    void SetSystemVolume(f32 volume_) {
        system_volume = volume_;
    }

    /**
     * Set the device volume.
     *
     * @param volume_ - The new device volume.
     */
    void SetDeviceVolume(f32 volume_) {
        device_volume = volume_;
    }

    /**
     * Get the number of queued audio buffers.
     *
     * @return The number of queued buffers.
     */
    u32 GetQueueSize() const {
        return queued_buffers.load();
    }

    /**
     * Set the maximum buffer queue size.
     */
    void SetRingSize(u32 ring_size) {
        max_queue_size = ring_size;
    }

    /**
     * Append a new buffer and its samples to a waiting queue to play.
     *
     * @param buffer  - Audio buffer information to be queued.
     * @param samples - The s16 samples to be queue for playback.
     */
    virtual void AppendBuffer(SinkBuffer& buffer, std::span<s16> samples);

    /**
     * Release a buffer. Audio In only, will fill a buffer with recorded samples.
     *
     * @param num_samples - Maximum number of samples to receive.
     * @return Vector of recorded samples. May have fewer than num_samples.
     */
    virtual std::vector<s16> ReleaseBuffer(u64 num_samples);

    /**
     * Empty out the buffer queue.
     */
    void ClearQueue();

    /**
     * Callback for AudioIn.
     *
     * @param input_buffer - Input buffer to be filled with samples.
     * @param num_frames - Number of frames to be filled.
     */
    void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);

    /**
     * Callback for AudioOut and AudioRenderer.
     *
     * @param output_buffer - Output buffer to be filled with samples.
     * @param num_frames - Number of frames to be filled.
     */
    void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);

    /**
     * Get the total number of samples expected to have been played by this stream.
     *
     * @return The number of samples.
     */
    u64 GetExpectedPlayedSampleCount();

    /**
     * Waits for free space in the sample ring buffer
     */
    void WaitFreeSpace(std::stop_token stop_token);

protected:
    /**
     * Unblocks the ADSP if the stream is paused.
     */
    void SignalPause();

protected:
    /// Core system
    Core::System& system;
    /// Type of this stream
    StreamType type;
    /// Set by the audio render/in/out system which uses this stream
    u32 system_channels{2};
    /// Channels supported by hardware
    u32 device_channels{2};
    /// Is this stream currently paused?
    std::atomic<bool> paused{true};
    /// Name of this stream
    std::string name{};

private:
    /// Ring buffer of the samples waiting to be played or consumed
    Common::RingBuffer<s16, 0x10000> samples_buffer;
    /// Audio buffers queued and waiting to play
    Common::ReaderWriterQueue<SinkBuffer> queue;
    /// The currently-playing audio buffer
    SinkBuffer playing_buffer{};
    /// The last played (or received) frame of audio, used when the callback underruns
    std::array<s16, MaxChannels> last_frame{};
    /// Number of buffers waiting to be played
    std::atomic<u32> queued_buffers{};
    /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
    u32 max_queue_size{};
    /// Locks access to sample count tracking info
    std::mutex sample_count_lock;
    /// Minimum number of total samples that have been played since the last callback
    u64 min_played_sample_count{};
    /// Maximum number of total samples that can be played since the last callback
    u64 max_played_sample_count{};
    /// The time the two above tracking variables were last written to
    std::chrono::nanoseconds last_sample_count_update_time{};
    /// Set by the audio render/in/out system which uses this stream
    f32 system_volume{1.0f};
    /// Set via IAudioDevice service calls
    f32 device_volume{1.0f};
    /// Signalled when ring buffer entries are consumed
    std::condition_variable_any release_cv;
    std::mutex release_mutex;
};

using SinkStreamPtr = std::unique_ptr<SinkStream>;

} // namespace AudioCore::Sink
