/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#include "AudioProxy.h"

#include <ATLCommon.h>
#include <SoundCVars.h>
#include <AudioLogger.h>
#include <MathConversion.h>

#include <ISystem.h>
#include <IEntitySystem.h>

namespace Audio
{
    extern CAudioLogger g_audioLogger;

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    struct AreaInfoCompare
    {
        bool operator() (const SAudioAreaInfo& oAreaInfo1, const SAudioAreaInfo& oAreaInfo2)
        {
            bool bResult = false;
            const int nGroup1 = oAreaInfo1.pArea->GetGroup();
            const int nGroup2 = oAreaInfo2.pArea->GetGroup();

            if (nGroup1 == nGroup2)
            {
                bResult = (oAreaInfo1.pArea->GetPriority() > oAreaInfo2.pArea->GetPriority());
            }
            else
            {
                bResult = (nGroup1 > nGroup2);
            }

            return bResult;
        }
    };

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    CAudioProxy::CAudioProxy()
        : m_nAudioObjectID(INVALID_AUDIO_OBJECT_ID)
        , m_nFlags(eAPF_NONE)
        , m_eCurrentLipSyncMethod(eLSM_None)
        , m_nCurrentLipSyncID(INVALID_AUDIO_CONTROL_ID)
    {
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    CAudioProxy::~CAudioProxy()
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) != 0)
        {
            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::RemoveRequestListener, &CAudioProxy::OnAudioEvent, this);
        }

        AZ_Assert(m_nAudioObjectID == INVALID_AUDIO_OBJECT_ID, "Expected AudioObjectID [%d] to be invalid when the audio proxy is destructed.", m_nAudioObjectID);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::Initialize(const char* const sObjectName, const bool bInitAsync /* = true */)
    {
        if ((bInitAsync && g_audioCVars.m_nAudioProxiesInitType == 0) || g_audioCVars.m_nAudioProxiesInitType == 2)
        {
            if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
            {
                // Add the request listener to receive callback when the audio object ID has been registered with middleware...
                AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::AddRequestListener, &CAudioProxy::OnAudioEvent, this, eART_AUDIO_MANAGER_REQUEST, eAMRT_RESERVE_AUDIO_OBJECT_ID);

                m_nFlags |= eAPF_WAITING_FOR_ID;

                SAudioRequest oRequest;
                SAudioManagerRequestData<eAMRT_RESERVE_AUDIO_OBJECT_ID> oRequestData(&m_nAudioObjectID, sObjectName);
                oRequest.nFlags = (eARF_PRIORITY_HIGH | eARF_SYNC_CALLBACK);
                oRequest.pOwner = this;
                oRequest.pData = &oRequestData;

                AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
            }
            else
            {
                SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_INITIALIZE);
                oQueuedCommand.sValue = sObjectName;
                TryAddQueuedCommand(oQueuedCommand);
            }
        }
        else
        {
            SAudioRequest oRequest;
            SAudioManagerRequestData<eAMRT_RESERVE_AUDIO_OBJECT_ID> oRequestData(&m_nAudioObjectID, sObjectName);
            oRequest.nFlags = (eARF_PRIORITY_HIGH | eARF_EXECUTE_BLOCKING);
            oRequest.pData = &oRequestData;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequestBlocking, oRequest);

    #if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
            if (m_nAudioObjectID == INVALID_AUDIO_OBJECT_ID)
            {
                g_audioLogger.Log(eALT_ASSERT, "Failed to reserve audio object ID on AudioProxy (%s)!", sObjectName);
            }
    #endif // INCLUDE_AUDIO_PRODUCTION_CODE
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::ExecuteSourceTrigger(
        TAudioControlID nTriggerID,
        const SAudioSourceInfo& rSourceInfo,
        const SAudioCallBackInfos& rCallbackInfos /* = SAudioCallBackInfos::GetEmptyObject() */)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            AZ_Assert(m_nAudioObjectID != INVALID_AUDIO_OBJECT_ID, "Invalid AudioObjectID found when an audio proxy is executing a source trigger!");

            SAudioRequest oRequest;
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = rCallbackInfos.nRequestFlags;

            SAudioObjectRequestData<eAORT_EXECUTE_SOURCE_TRIGGER> oRequestData(nTriggerID, rSourceInfo);
            oRequest.pOwner = (rCallbackInfos.pObjectToNotify != nullptr) ? rCallbackInfos.pObjectToNotify : this;
            oRequest.pUserData = rCallbackInfos.pUserData;
            oRequest.pUserDataOwner = rCallbackInfos.pUserDataOwner;
            oRequest.pData = &oRequestData;
            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_EXECUTE_SOURCE_TRIGGER);
            oQueuedCommand.nTriggerID = nTriggerID;
            oQueuedCommand.pOwnerOverride = rCallbackInfos.pObjectToNotify;
            oQueuedCommand.pUserData = rCallbackInfos.pUserData;
            oQueuedCommand.pUserDataOwner = rCallbackInfos.pUserDataOwner;
            oQueuedCommand.nRequestFlags = rCallbackInfos.nRequestFlags;
            oQueuedCommand.rSourceInfo = rSourceInfo;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::ExecuteTrigger(
        const TAudioControlID nTriggerID,
        const ELipSyncMethod eLipSyncMethod,
        const SAudioCallBackInfos& rCallbackInfos /* = SAudioCallBackInfos::GetEmptyObject() */)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            AZ_Assert(m_nAudioObjectID != INVALID_AUDIO_OBJECT_ID, "Invalid AudioObjectID found when an audio proxy is executing a trigger!");

            SAudioRequest oRequest;
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = rCallbackInfos.nRequestFlags;

            SAudioObjectRequestData<eAORT_EXECUTE_TRIGGER> oRequestData(nTriggerID, 0.0f);
            oRequestData.eLipSyncMethod = eLipSyncMethod;
            oRequest.pOwner = (rCallbackInfos.pObjectToNotify != nullptr) ? rCallbackInfos.pObjectToNotify : this;
            oRequest.pUserData = rCallbackInfos.pUserData;
            oRequest.pUserDataOwner = rCallbackInfos.pUserDataOwner;
            oRequest.pData = &oRequestData;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_EXECUTE_TRIGGER);
            oQueuedCommand.nTriggerID = nTriggerID;
            oQueuedCommand.eLipSyncMethod = eLipSyncMethod;
            oQueuedCommand.pOwnerOverride = rCallbackInfos.pObjectToNotify;
            oQueuedCommand.pUserData = rCallbackInfos.pUserData;
            oQueuedCommand.pUserDataOwner = rCallbackInfos.pUserDataOwner;
            oQueuedCommand.nRequestFlags = rCallbackInfos.nRequestFlags;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::StopAllTriggers()
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest oRequest;
            SAudioObjectRequestData<eAORT_STOP_ALL_TRIGGERS> oRequestData;
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = eARF_PRIORITY_NORMAL;
            oRequest.pData = &oRequestData;
            oRequest.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_STOP_ALL_TRIGGERS);
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::StopTrigger(const TAudioControlID nTriggerID)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest oRequest;
            SAudioObjectRequestData<eAORT_STOP_TRIGGER> oRequestData(nTriggerID);
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = eARF_PRIORITY_NORMAL;
            oRequest.pData = &oRequestData;
            oRequest.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_STOP_TRIGGER);
            oQueuedCommand.nTriggerID = nTriggerID;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetSwitchState(const TAudioControlID nSwitchID, const TAudioSwitchStateID nStateID)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest oRequest;
            SAudioObjectRequestData<eAORT_SET_SWITCH_STATE> oRequestData(nSwitchID, nStateID);
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = eARF_PRIORITY_NORMAL;
            oRequest.pData = &oRequestData;
            oRequest.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_SET_SWITCH_STATE);
            oQueuedCommand.nSwitchID = nSwitchID;
            oQueuedCommand.nStateID = nStateID;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetRtpcValue(const TAudioControlID nRtpcID, const float fValue)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest oRequest;
            SAudioObjectRequestData<eAORT_SET_RTPC_VALUE> oRequestData(nRtpcID, fValue);
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = eARF_PRIORITY_NORMAL;
            oRequest.pData = &oRequestData;
            oRequest.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_SET_RTPC_VALUE);
            oQueuedCommand.nRtpcID = nRtpcID;
            oQueuedCommand.fValue = fValue;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetObstructionCalcType(const EAudioObjectObstructionCalcType eObstructionType)
    {
        const size_t nObstructionCalcIndex = static_cast<size_t>(eObstructionType);

        if (nObstructionCalcIndex < AZ_ARRAY_SIZE(ATLInternalControlIDs::OOCStateIDs))
        {
            SetSwitchState(ATLInternalControlIDs::ObstructionOcclusionCalcSwitchID, ATLInternalControlIDs::OOCStateIDs[nObstructionCalcIndex]);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetPosition(const SATLWorldPosition& refPosition)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            // Update position only if the delta exceeds a given value.
            if (g_audioCVars.m_fPositionUpdateThreshold <= 0.f      // <-- no gating
                || !refPosition.GetPositionVec().IsClose(m_oPosition.GetPositionVec(), g_audioCVars.m_fPositionUpdateThreshold))
            {
                m_oPosition = refPosition;

                // Make sure the forward/up directions are normalized
                m_oPosition.NormalizeForwardVec();
                m_oPosition.NormalizeUpVec();

                SAudioRequest oRequest;
                SAudioObjectRequestData<eAORT_SET_POSITION> oRequestData(m_oPosition);
                oRequest.nAudioObjectID = m_nAudioObjectID;
                oRequest.nFlags = eARF_PRIORITY_NORMAL;
                oRequest.pData = &oRequestData;
                oRequest.pOwner = this;

                AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
            }
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_SET_POSITION);
            oQueuedCommand.oPosition = refPosition;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetPosition(const AZ::Vector3& refPosition)
    {
        SetPosition(SATLWorldPosition(refPosition));
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetMultiplePositions(const MultiPositionParams& params)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest request;
            SAudioObjectRequestData<eAORT_SET_MULTI_POSITIONS> requestData(params);
            request.nAudioObjectID = m_nAudioObjectID;
            request.nFlags = eARF_PRIORITY_NORMAL;
            request.pData = &requestData;
            request.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, request);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_SET_MULTI_POSITIONS);
            oQueuedCommand.oMultiPosParams= params;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetEnvironmentAmount(const TAudioEnvironmentID nEnvironmentID, const float fValue)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest oRequest;
            SAudioObjectRequestData<eAORT_SET_ENVIRONMENT_AMOUNT> oRequestData(nEnvironmentID, fValue);
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = eARF_PRIORITY_NORMAL;
            oRequest.pData = &oRequestData;
            oRequest.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_SET_ENVIRONMENT_AMOUNT);
            oQueuedCommand.nEnvironmentID = nEnvironmentID;
            oQueuedCommand.fValue = fValue;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetCurrentEnvironments(const EntityId nEntityToIgnore)
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            if (!gEnv->pEntitySystem)
            {
                return;
            }

            IAreaManager* const pAreaManager = gEnv->pEntitySystem->GetAreaManager();

            SAudioAreaInfo m_aAreaQueries[sMaxAreas];
            size_t nAreaCount = 0;

            ClearEnvironments();

            if (!pAreaManager->QueryAudioAreas(AZVec3ToLYVec3(m_oPosition.GetPositionVec()), m_aAreaQueries, sMaxAreas, nAreaCount))
            {
                g_audioLogger.Log(eALT_WARNING, "QueryAreas failed for AudioObjectID %u", m_nAudioObjectID);
            }

            if (nAreaCount > 0)
            {
                std::sort(m_aAreaQueries, m_aAreaQueries + nAreaCount, AreaInfoCompare());

                int nLastGroupID = -1;// default value for the CArea::m_AreaGroupID
                int nLastPriority = 0;// default value for the CArea::m_nPriority
                bool bIgnoreThisGroup = false;
                float fCurrentAmount = 0.0f;

                for (size_t nArea = 0; nArea < nAreaCount; ++nArea)
                {
                    const SAudioAreaInfo& rAreaInfo = m_aAreaQueries[nArea];
                    const int nCurrentGroupID = rAreaInfo.pArea->GetGroup();
                    const int nCurrentPriority = rAreaInfo.pArea->GetPriority();

                    if (nEntityToIgnore == INVALID_ENTITYID || nEntityToIgnore != rAreaInfo.nEnvProvidingEntityID)
                    {
                        if (nCurrentGroupID != nLastGroupID)
                        {
                            // new group, highest priority
                            fCurrentAmount = rAreaInfo.fEnvironmentAmount;
                            nLastGroupID = nCurrentGroupID;
                            nLastPriority = nCurrentPriority;

                            SetEnvironmentAmount(rAreaInfo.nEnvironmentID, rAreaInfo.fEnvironmentAmount);
                        }
                        else if (nCurrentPriority != nLastPriority)
                        {
                            // same group, lower priority
                            if (fCurrentAmount < 1.0f)
                            {
                                fCurrentAmount = rAreaInfo.fEnvironmentAmount;
                                nLastPriority = nCurrentPriority;

                                SetEnvironmentAmount(rAreaInfo.nEnvironmentID, fCurrentAmount);
                            }
                        }
                        else
                        {
                            // same group, same priority
                            // this assumes that all areas of the same priority within a group use the same Environment
                            const float fAmount = rAreaInfo.fEnvironmentAmount;

                            if (fAmount > fCurrentAmount)
                            {
                                SetEnvironmentAmount(rAreaInfo.nEnvironmentID, fAmount);
                                fCurrentAmount = fAmount;
                            }
                        }
                    }
                }
            }
        }
        else
        {
            SQueuedAudioCommand oQueuedCommand = SQueuedAudioCommand(eQACT_SET_CURRENT_ENVIRONMENTS);
            oQueuedCommand.nEntityID = nEntityToIgnore;
            TryAddQueuedCommand(oQueuedCommand);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::SetLipSyncProvider(ILipSyncProvider* const pILipSyncProvider)
    {
        if (pILipSyncProvider == m_oLipSyncProvider.get())
        {
            return;
        }

        m_oLipSyncProvider.reset(pILipSyncProvider);
        m_nCurrentLipSyncID = INVALID_AUDIO_CONTROL_ID;
        m_eCurrentLipSyncMethod = eLSM_None;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::ClearEnvironments()
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest oRequest;
            SAudioObjectRequestData<eAORT_RESET_ENVIRONMENTS> oRequestData;
            oRequest.nAudioObjectID = m_nAudioObjectID;
            oRequest.nFlags = eARF_PRIORITY_NORMAL;
            oRequest.pData = &oRequestData;
            oRequest.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);
        }
        else
        {
            TryAddQueuedCommand(SQueuedAudioCommand(eQACT_CLEAR_ENVIRONMENTS));
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::ResetRtpcValues()
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            SAudioRequest request;
            SAudioObjectRequestData<eAORT_RESET_RTPCS> requestData;
            request.nAudioObjectID = m_nAudioObjectID;
            request.nFlags = eARF_PRIORITY_NORMAL;
            request.pData = &requestData;
            request.pOwner = this;

            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, request);
        }
        else
        {
            TryAddQueuedCommand(SQueuedAudioCommand(eQACT_CLEAR_RTPCS));
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::Release()
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            Reset();
            AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::FreeAudioProxy, this);
        }
        else
        {
            TryAddQueuedCommand(SQueuedAudioCommand(eQACT_RELEASE));
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::Reset()
    {
        if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
        {
            if (m_nAudioObjectID != INVALID_AUDIO_OBJECT_ID)
            {
                // Request must be asynchronous and lowest priority!
                SAudioRequest oRequest;
                SAudioObjectRequestData<eAORT_RELEASE_OBJECT> oRequestData;
                oRequest.nAudioObjectID = m_nAudioObjectID;
                oRequest.pData = &oRequestData;

                AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, oRequest);

                m_nAudioObjectID = INVALID_AUDIO_OBJECT_ID;
            }

            m_oPosition = SATLWorldPosition();
            m_eCurrentLipSyncMethod = eLSM_None;
            m_nCurrentLipSyncID = INVALID_AUDIO_CONTROL_ID;
        }
        else
        {
            TryAddQueuedCommand(SQueuedAudioCommand(eQACT_RESET));
        }
    }

    // Audio: need a way to periodically update the LipSyncProvider
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    //void CAudioProxy::Update(SEntityUpdateContext &ctx)
    //{
    //  if (m_currentLipSyncId != INVALID_AUDIO_CONTROL_ID)
    //  {
    //      if (m_pLipSyncProvider)
    //      {
    //          m_pLipSyncProvider->UpdateLipSync(this, m_currentLipSyncId, m_currentLipSyncMethod);
    //      }
    //  }
    //}

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::ExecuteQueuedCommands()
    {
        // Remove the request listener once the audio system has properly reserved the audio object ID for this proxy.
        AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::RemoveRequestListener, &CAudioProxy::OnAudioEvent, this);

        m_nFlags &= ~eAPF_WAITING_FOR_ID;

        if (!m_aQueuedAudioCommands.empty())
        {
            TQueuedAudioCommands::iterator Iter(m_aQueuedAudioCommands.begin());
            TQueuedAudioCommands::const_iterator IterEnd(m_aQueuedAudioCommands.end());
            for (; Iter != IterEnd; ++Iter)
            {
                const SQueuedAudioCommand& refCommand = (*Iter);
                // TODO: pass the refCommand.pUserData to all methods (maybe pass refCommand as a parameter to all functions)

                switch (refCommand.eType)
                {
                    case eQACT_EXECUTE_TRIGGER:
                    {
                        const SAudioCallBackInfos callbackInfos(refCommand.pOwnerOverride, refCommand.pUserData, refCommand.pUserDataOwner, refCommand.nRequestFlags);
                        ExecuteTrigger(refCommand.nTriggerID, refCommand.eLipSyncMethod, callbackInfos);
                        break;
                    }
                    case eQACT_EXECUTE_SOURCE_TRIGGER:
                    {
                        const SAudioCallBackInfos callbackInfos(refCommand.pOwnerOverride, refCommand.pUserData, refCommand.pUserDataOwner, refCommand.nRequestFlags);
                        ExecuteSourceTrigger(refCommand.nTriggerID, refCommand.rSourceInfo, callbackInfos);
                        break;
                    }
                    case eQACT_STOP_TRIGGER:
                    {
                        StopTrigger(refCommand.nTriggerID);
                        break;
                    }
                    case eQACT_SET_SWITCH_STATE:
                    {
                        SetSwitchState(refCommand.nSwitchID, refCommand.nStateID);
                        break;
                    }
                    case eQACT_SET_RTPC_VALUE:
                    {
                        SetRtpcValue(refCommand.nRtpcID, refCommand.fValue);
                        break;
                    }
                    case eQACT_SET_POSITION:
                    {
                        SetPosition(refCommand.oPosition);
                        break;
                    }
                    case eQACT_SET_ENVIRONMENT_AMOUNT:
                    {
                        SetEnvironmentAmount(refCommand.nEnvironmentID, refCommand.fValue);
                        break;
                    }
                    case eQACT_SET_CURRENT_ENVIRONMENTS:
                    {
                        SetCurrentEnvironments(refCommand.nEntityID);
                        break;
                    }
                    case eQACT_CLEAR_ENVIRONMENTS:
                    {
                        ClearEnvironments();
                        break;
                    }
                    case eQACT_CLEAR_RTPCS:
                    {
                        ResetRtpcValues();
                        break;
                    }
                    case eQACT_RESET:
                    {
                        Reset();
                        break;
                    }
                    case eQACT_RELEASE:
                    {
                        Release();
                        break;
                    }
                    case eQACT_INITIALIZE:
                    {
                        Initialize(refCommand.sValue.c_str(), true);
                        break;
                    }
                    case eQACT_STOP_ALL_TRIGGERS:
                    {
                        StopAllTriggers();
                        break;
                    }
                    case eQACT_SET_MULTI_POSITIONS:
                    {
                        SetMultiplePositions(refCommand.oMultiPosParams);
                        break;
                    }
                    default:
                    {
                        g_audioLogger.Log(eALT_ASSERT, "Unknown command type in CAudioProxy::ExecuteQueuedCommands!");
                        break;
                    }
                }

                if ((m_nFlags & eAPF_WAITING_FOR_ID) != 0)
                {
                    // An Initialize command was queued up.
                    // Here we need to keep all commands after the Initialize.
                    break;
                }
            }

            if ((m_nFlags & eAPF_WAITING_FOR_ID) == 0)
            {
                m_aQueuedAudioCommands.clear();
            }
            else
            {
                // An Initialize command was queued up.
                // Here we need to keep queued commands except for Reset and Initialize.
                Iter = m_aQueuedAudioCommands.begin();

                while (Iter != IterEnd)
                {
                    const SQueuedAudioCommand& refCommand = (*Iter);

                    if (refCommand.eType == eQACT_RESET || refCommand.eType == eQACT_INITIALIZE)
                    {
                        Iter = m_aQueuedAudioCommands.erase(Iter);
                        IterEnd = m_aQueuedAudioCommands.end();
                        continue;
                    }

                    ++Iter;
                }
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::TryAddQueuedCommand(const SQueuedAudioCommand& refCommand)
    {
        bool bAdd = true;

        switch (refCommand.eType)
        {
            case eQACT_EXECUTE_TRIGGER:
            case eQACT_EXECUTE_SOURCE_TRIGGER:
            case eQACT_STOP_TRIGGER:
            {
                // These type of commands get always pushed back!
                break;
            }
            case eQACT_SET_SWITCH_STATE:
            {
                if (!m_aQueuedAudioCommands.empty())
                {
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.begin(), m_aQueuedAudioCommands.end(), SFindSetSwitchState(refCommand.nSwitchID, refCommand.nStateID)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            case eQACT_SET_RTPC_VALUE:
            {
                if (!m_aQueuedAudioCommands.empty())
                {
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.begin(), m_aQueuedAudioCommands.end(), SFindSetRtpcValue(refCommand.nRtpcID, refCommand.fValue)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            case eQACT_SET_POSITION:
            {
                if (!m_aQueuedAudioCommands.empty())
                {
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.begin(), m_aQueuedAudioCommands.end(), SFindSetPosition(refCommand.oPosition)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            case eQACT_SET_ENVIRONMENT_AMOUNT:
            {
                if (!m_aQueuedAudioCommands.empty())
                {
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.begin(), m_aQueuedAudioCommands.end(), SFindSetEnvironmentAmount(refCommand.nEnvironmentID, refCommand.fValue)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            case eQACT_SET_CURRENT_ENVIRONMENTS:
            case eQACT_CLEAR_ENVIRONMENTS:
            case eQACT_CLEAR_RTPCS:
            case eQACT_RELEASE:
            {
                // These type of commands don't need another instance!
                if (!m_aQueuedAudioCommands.empty())
                {
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.begin(), m_aQueuedAudioCommands.end(), SFindCommand(refCommand.eType)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            case eQACT_RESET:
            {
                for (const SQueuedAudioCommand& rLocalCommand : m_aQueuedAudioCommands)
                {
                    if (rLocalCommand.eType == eQACT_RELEASE)
                    {
                        // If eQACT_RELEASE is already queued up then there is no need for adding a eQACT_RESET command.
                        bAdd = false;
                        break;
                    }
                }

                if (!bAdd)
                {
                    // If this proxy is resetting then there is no need for any pending commands.
                    m_aQueuedAudioCommands.clear();
                }
                break;
            }
            case eQACT_INITIALIZE:
            {
                // There must be only 1 Initialize command be queued up.
                m_aQueuedAudioCommands.clear();

                // Precede the Initialization with a Reset command to release the pending audio object.
                m_aQueuedAudioCommands.push_back(SQueuedAudioCommand(eQACT_RESET));
                break;
            }
            case eQACT_STOP_ALL_TRIGGERS:
            {
                if (!m_aQueuedAudioCommands.empty())
                {
                    // only add if the last request is different...
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.end() - 1, m_aQueuedAudioCommands.end(), SFindCommand(refCommand.eType)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            case eQACT_SET_MULTI_POSITIONS:
            {
                if (!m_aQueuedAudioCommands.empty())
                {
                    // Find+Update or Add.
                    // Can morph a SetPosition command into a Multi-Position command.
                    bAdd = (AZStd::find_if(m_aQueuedAudioCommands.begin(), m_aQueuedAudioCommands.end(), SFindSetMultiplePositions(refCommand.oMultiPosParams)) == m_aQueuedAudioCommands.end());
                }
                break;
            }
            default:
            {
                g_audioLogger.Log(eALT_ERROR, "Unknown queued command type [%d] in CAudioProxy::TryAddQueuedCommand!", refCommand.eType);
                bAdd = false;
                break;
            }
        }

        if (bAdd)
        {
            if (refCommand.eType == eQACT_SET_POSITION || refCommand.eType == eQACT_SET_MULTI_POSITIONS)
            {
                // Make sure we set position first!
                m_aQueuedAudioCommands.push_front(refCommand);
            }
            else
            {
                m_aQueuedAudioCommands.push_back(refCommand);
            }
        }
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioProxy::OnAudioEvent(const SAudioRequestInfo* const pAudioRequestInfo)
    {
        if (pAudioRequestInfo->eResult == eARR_SUCCESS && pAudioRequestInfo->eAudioRequestType == eART_AUDIO_MANAGER_REQUEST)
        {
            const auto eAudioManagerRequestType = static_cast<const EAudioManagerRequestType>(pAudioRequestInfo->nSpecificAudioRequest);

            if (eAudioManagerRequestType == eAMRT_RESERVE_AUDIO_OBJECT_ID)
            {
                auto const pAudioProxy = static_cast<CAudioProxy*>(pAudioRequestInfo->pOwner);

                if (pAudioProxy)
                {
                    pAudioProxy->ExecuteQueuedCommands();
                }
            }
        }
    }

} // namespace Audio