#ifndef BOO_AUDIOSUBMIX_HPP
#define BOO_AUDIOSUBMIX_HPP

#include "boo/audiodev/IAudioSubmix.hpp"
#include <list>
#include <vector>
#include  <array>
#include <unordered_map>
#include "Common.hpp"

#if __SSE__
#include <immintrin.h>
#endif

struct AudioUnitVoiceEngine;
struct VSTVoiceEngine;
struct WAVOutVoiceEngine;

namespace boo
{
class BaseAudioVoiceEngine;
class AudioVoice;
struct AudioVoiceEngineMixInfo;
/* Output gains for each mix-send/channel */

class AudioSubmix : public ListNode<AudioSubmix, BaseAudioVoiceEngine*, IAudioSubmix>
{
    friend class BaseAudioVoiceEngine;
    friend class AudioVoiceMono;
    friend class AudioVoiceStereo;
    friend struct WASAPIAudioVoiceEngine;
    friend struct ::AudioUnitVoiceEngine;
    friend struct ::VSTVoiceEngine;
    friend struct ::WAVOutVoiceEngine;

    /* Mixer-engine relationships */
    int m_busId;
    bool m_mainOut;

    /* Callback (effect source, optional) */
    IAudioSubmixCallback* m_cb;

    /* Slew state for output gains */
    size_t m_slewFrames = 0;
    size_t m_curSlewFrame = 0;

    /* Output gains for each mix-send/channel */
    std::unordered_map<IAudioSubmix*, std::array<float, 2>> m_sendGains;

    /* Temporary scratch buffers for accumulating submix audio */
    std::vector<int16_t> m_scratch16;
    std::vector<int32_t> m_scratch32;
    std::vector<float> m_scratchFlt;
    template <typename T> std::vector<T>& _getScratch();

    /* Override scratch buffers with alternate destination */
    int16_t* m_redirect16 = nullptr;
    int32_t* m_redirect32 = nullptr;
    float* m_redirectFlt = nullptr;
    template <typename T> T*& _getRedirect();

    /* C3-linearization support (to mitigate a potential diamond problem on 'clever' submix routes) */
    bool _isDirectDependencyOf(AudioSubmix* send);
    std::list<AudioSubmix*> _linearizeC3();
    static bool _mergeC3(std::list<AudioSubmix*>& output,
                         std::vector<std::list<AudioSubmix*>>& lists);

    /* Fill scratch buffers with silence for new mix cycle */
    template <typename T> void _zeroFill();

    /* Receive audio from a single voice / submix */
    template <typename T> T* _getMergeBuf(size_t frames);

    /* Mix scratch buffers into sends */
    template <typename T> size_t _pumpAndMix(size_t frames);

    void _resetOutputSampleRate();

public:
    static AudioSubmix*& _getHeadPtr(BaseAudioVoiceEngine* head);
    static std::unique_lock<std::recursive_mutex> _getHeadLock(BaseAudioVoiceEngine* head);
    std::unique_lock<std::recursive_mutex> destructorLock();

    AudioSubmix(BaseAudioVoiceEngine& root, IAudioSubmixCallback* cb, int busId, bool mainOut);
    ~AudioSubmix();

    void resetSendLevels();
    void setSendLevel(IAudioSubmix* submix, float level, bool slew);
    const AudioVoiceEngineMixInfo& mixInfo() const;
    double getSampleRate() const;
    SubmixFormat getSampleFormat() const;
};

template <> inline std::vector<int16_t>& AudioSubmix::_getScratch() { return m_scratch16; }
template <> inline std::vector<int32_t>& AudioSubmix::_getScratch() { return m_scratch32; }
template <> inline std::vector<float>& AudioSubmix::_getScratch() { return m_scratchFlt; }

template <> inline int16_t*& AudioSubmix::_getRedirect<int16_t>() { return m_redirect16; }
template <> inline int32_t*& AudioSubmix::_getRedirect<int32_t>() { return m_redirect32; }
template <> inline float*& AudioSubmix::_getRedirect<float>() { return m_redirectFlt; }

}

#endif // BOO_AUDIOSUBMIX_HPP