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

#pragma once

#include <array>
#include <bitset>

#include "audio_core/common/common.h"
#include "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/memory/address_info.h"
#include "common/common_types.h"

namespace AudioCore::Renderer {
class PoolMapper;
class VoiceContext;
struct VoiceState;

/**
 * Represents one voice. Voices are essentially noises, and they can be further mixed and have
 * effects applied to them, but voices are the basis of all sounds.
 */
class VoiceInfo {
public:
    enum class ServerPlayState {
        Started,
        Stopped,
        RequestStop,
        Paused,
    };

    struct Flags {
        u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
        u8 IsVoicePitchAndSrcSkippedSupported : 1;
    };

    /**
     * A wavebuffer contains information on the data source buffers.
     */
    struct WaveBuffer {
        void Copy(WaveBufferVersion1& other) {
            other.buffer = buffer_address.GetReference(true);
            other.buffer_size = buffer_address.GetSize();
            other.start_offset = start_offset;
            other.end_offset = end_offset;
            other.loop = loop;
            other.stream_ended = stream_ended;

            if (context_address.GetCpuAddr()) {
                other.context = context_address.GetReference(true);
                other.context_size = context_address.GetSize();
            } else {
                other.context = CpuAddr(0);
                other.context_size = 0;
            }
        }

        void Copy(WaveBufferVersion2& other) {
            other.buffer = buffer_address.GetReference(true);
            other.buffer_size = buffer_address.GetSize();
            other.start_offset = start_offset;
            other.end_offset = end_offset;
            other.loop_start_offset = loop_start_offset;
            other.loop_end_offset = loop_end_offset;
            other.loop = loop;
            other.loop_count = loop_count;
            other.stream_ended = stream_ended;

            if (context_address.GetCpuAddr()) {
                other.context = context_address.GetReference(true);
                other.context_size = context_address.GetSize();
            } else {
                other.context = CpuAddr(0);
                other.context_size = 0;
            }
        }

        void Initialize() {
            buffer_address.Setup(0, 0);
            context_address.Setup(0, 0);
            start_offset = 0;
            end_offset = 0;
            loop = false;
            stream_ended = false;
            sent_to_DSP = true;
            loop_start_offset = 0;
            loop_end_offset = 0;
            loop_count = 0;
        }
        /// Game memory address of the wavebuffer data
        AddressInfo buffer_address{0, 0};
        /// Context for decoding, used for ADPCM
        AddressInfo context_address{0, 0};
        /// Starting offset for the wavebuffer
        u32 start_offset{};
        /// Ending offset the wavebuffer
        u32 end_offset{};
        /// Should this wavebuffer loop?
        bool loop{};
        /// Has this wavebuffer ended?
        bool stream_ended{};
        /// Has this wavebuffer been sent to the AudioRenderer?
        bool sent_to_DSP{true};
        /// Starting offset when looping, can differ from start_offset
        u32 loop_start_offset{};
        /// Ending offset when looping, can differ from end_offset
        u32 loop_end_offset{};
        /// Number of times to loop this wavebuffer
        s32 loop_count{};
    };

    struct WaveBufferInternal {
        /* 0x00 */ CpuAddr address;
        /* 0x08 */ u64 size;
        /* 0x10 */ s32 start_offset;
        /* 0x14 */ s32 end_offset;
        /* 0x18 */ bool loop;
        /* 0x19 */ bool stream_ended;
        /* 0x1A */ bool sent_to_DSP;
        /* 0x1C */ s32 loop_count;
        /* 0x20 */ CpuAddr context_address;
        /* 0x28 */ u64 context_size;
        /* 0x30 */ u32 loop_start;
        /* 0x34 */ u32 loop_end;
    };
    static_assert(sizeof(WaveBufferInternal) == 0x38,
                  "VoiceInfo::WaveBufferInternal has the wrong size!");

    struct BiquadFilterParameter {
        /* 0x00 */ bool enabled;
        /* 0x02 */ std::array<s16, 3> b;
        /* 0x08 */ std::array<s16, 2> a;
    };
    static_assert(sizeof(BiquadFilterParameter) == 0xC,
                  "VoiceInfo::BiquadFilterParameter has the wrong size!");

    struct InParameter {
        /* 0x000 */ u32 id;
        /* 0x004 */ u32 node_id;
        /* 0x008 */ bool is_new;
        /* 0x009 */ bool in_use;
        /* 0x00A */ PlayState play_state;
        /* 0x00B */ SampleFormat sample_format;
        /* 0x00C */ u32 sample_rate;
        /* 0x010 */ s32 priority;
        /* 0x014 */ s32 sort_order;
        /* 0x018 */ u32 channel_count;
        /* 0x01C */ f32 pitch;
        /* 0x020 */ f32 volume;
        /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
        /* 0x03C */ u32 wave_buffer_count;
        /* 0x040 */ u16 wave_buffer_index;
        /* 0x042 */ char unk042[0x6];
        /* 0x048 */ CpuAddr src_data_address;
        /* 0x050 */ u64 src_data_size;
        /* 0x058 */ u32 mix_id;
        /* 0x05C */ u32 splitter_id;
        /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
        /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
        /* 0x158 */ bool clear_voice_drop;
        /* 0x159 */ u8 flush_buffer_count;
        /* 0x15A */ char unk15A[0x2];
        /* 0x15C */ Flags flags;
        /* 0x15D */ char unk15D[0x1];
        /* 0x15E */ SrcQuality src_quality;
        /* 0x15F */ char unk15F[0x11];
    };
    static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");

    struct OutStatus {
        /* 0x00 */ u64 played_sample_count;
        /* 0x08 */ u32 wave_buffers_consumed;
        /* 0x0C */ bool voice_dropped;
    };
    static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");

    VoiceInfo();

    /**
     * Initialize this voice.
     */
    void Initialize();

    /**
     * Does this voice need an update?
     *
     * @param params - Input parameters to check matching.
     *
     * @return True if this voice needs an update, otherwise false.
     */
    bool ShouldUpdateParameters(const InParameter& params) const;

    /**
     * Update the parameters of this voice.
     *
     * @param error_info  - Output error code.
     * @param params      - Input parameters to update from.
     * @param pool_mapper - Used to map buffers.
     * @param behavior    - behavior to check supported features.
     */
    void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
                          const PoolMapper& pool_mapper, const BehaviorInfo& behavior);

    /**
     * Update the current play state.
     *
     * @param state - New play state for this voice.
     */
    void UpdatePlayState(PlayState state);

    /**
     * Update the current sample rate conversion quality.
     *
     * @param quality - New quality.
     */
    void UpdateSrcQuality(SrcQuality quality);

    /**
     * Update all wavebuffers.
     *
     * @param error_infos  - Output 2D array of errors, 2 per wavebuffer.
     * @param error_count  - Number of errors provided. Unused.
     * @param params       - Input parameters to be used for the update.
     * @param voice_states - The voice states for each channel in this voice to be updated.
     * @param pool_mapper  - Used to map the wavebuffers.
     * @param behavior     - Used to check for supported features.
     */
    void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
                           u32 error_count, const InParameter& params,
                           std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
                           const BehaviorInfo& behavior);

    /**
     * Update a wavebuffer.
     *
     * @param error_info           - Output array of errors.
     * @param wave_buffer          - The wavebuffer to be updated.
     * @param wave_buffer_internal - Input parameters to be used for the update.
     * @param sample_format        - Sample format of the wavebuffer.
     * @param valid                - Is this wavebuffer valid?
     * @param pool_mapper          - Used to map the wavebuffers.
     * @param behavior             - Used to check for supported features.
     */
    void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
                          const WaveBufferInternal& wave_buffer_internal,
                          SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
                          const BehaviorInfo& behavior);

    /**
     * Check if the input wavebuffer needs an update.
     *
     * @param wave_buffer_internal - Input wavebuffer parameters to check.
     * @return True if the given wavebuffer needs an update, otherwise false.
     */
    bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;

    /**
     * Write the number of played samples, number of consumed wavebuffers and if this voice was
     * dropped, to the given out_status.
     *
     * @param out_status   - Output status to be written to.
     * @param in_params    - Input parameters to check if the wavebuffer is new.
     * @param voice_states - Current host voice states for this voice, source of the output.
     */
    void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
                        std::span<VoiceState*> voice_states);

    /**
     * Check if this voice should be skipped for command generation.
     * Checks various things such as usage state, whether data is mapped etc.
     *
     * @return True if this voice should not be generated, otherwise false.
     */
    bool ShouldSkip() const;

    /**
     * Check if this voice has any mixing connections.
     *
     * @return True if this voice participates in mixing, otherwise false.
     */
    bool HasAnyConnection() const;

    /**
     * Flush flush_count wavebuffers, marking them as consumed.
     *
     * @param flush_count   - Number of wavebuffers to flush.
     * @param voice_states  - Voice states for these wavebuffers.
     * @param channel_count - Number of active channels.
     */
    void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);

    /**
     * Update this voice's parameters on command generation,
     * updating voice states and flushing if needed.
     *
     * @param voice_states  - Voice states for these wavebuffers.
     * @return True if this voice should be generated, otherwise false.
     */
    bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);

    /**
     * Update this voice on command generation.
     *
     * @param voice_context - Voice context for these wavebuffers.
     *
     * @return True if this voice should be generated, otherwise false.
     */
    bool UpdateForCommandGeneration(VoiceContext& voice_context);

    /**
     * Reset the AudioRenderer-side voice states, and the channel resources for this voice.
     *
     * @param voice_context - Context from which to get the resources.
     */
    void ResetResources(VoiceContext& voice_context) const;

    /// Is this voice in use?
    bool in_use{};
    /// Is this voice new?
    bool is_new{};
    /// Was this voice last playing? Used for depopping
    bool was_playing{};
    /// Sample format of the wavebuffers in this voice
    SampleFormat sample_format{};
    /// Sample rate of the wavebuffers in this voice
    u32 sample_rate{};
    /// Number of channels in this voice
    s8 channel_count{};
    /// Id of this voice
    u32 id{};
    /// Node id of this voice
    u32 node_id{};
    /// Mix id this voice is mixed to
    u32 mix_id{};
    /// Play state of this voice
    ServerPlayState current_play_state{ServerPlayState::Stopped};
    /// Last play state of this voice
    ServerPlayState last_play_state{ServerPlayState::Started};
    /// Priority of this voice, lower is higher
    s32 priority{};
    /// Sort order of this voice, used when same priority
    s32 sort_order{};
    /// Pitch of this voice (for sample rate conversion)
    f32 pitch{};
    /// Current volume of this voice
    f32 volume{};
    /// Previous volume of this voice
    f32 prev_volume{};
    /// Biquad filters for generating filter commands on this voice
    std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
    /// Number of active wavebuffers
    u32 wave_buffer_count{};
    /// Current playing wavebuffer index
    u16 wave_buffer_index{};
    /// Flags controlling decode behavior
    u16 flags{};
    /// Game memory for ADPCM coefficients
    AddressInfo data_address{0, 0};
    /// Wavebuffers
    std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
    /// Channel resources for this voice
    std::array<u32, MaxChannels> channel_resource_ids{};
    /// Splitter id this voice is connected with
    s32 splitter_id{UnusedSplitterId};
    /// Sample rate conversion quality
    SrcQuality src_quality{SrcQuality::Medium};
    /// Was this voice dropped due to limited time?
    bool voice_dropped{};
    /// Is this voice's coefficient (data_address) unmapped?
    bool data_unmapped{};
    /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
    bool buffer_unmapped{};
    /// Initialisation state of the biquads
    std::array<bool, MaxBiquadFilters> biquad_initialized{};
    /// Number of wavebuffers to flush
    u8 flush_buffer_count{};
};

} // namespace AudioCore::Renderer
