/*
* 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 <ATL.h>

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
    #include <AzCore/Math/Color.h>
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

#include <AzCore/StringFunc/StringFunc.h>

#include <SoundCVars.h>
#include <AudioProxy.h>
#include <ATLAudioObject.h>
#include <IAudioSystemImplementation.h>

#include <ISystem.h>
#include <IPhysics.h>
#include <IRenderAuxGeom.h>

namespace Audio
{
    extern CAudioLogger g_audioLogger;

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    inline EAudioRequestResult ConvertToRequestResult(const EAudioRequestStatus eAudioRequestStatus)
    {
        EAudioRequestResult eResult;

        switch (eAudioRequestStatus)
        {
            case eARS_SUCCESS:
            {
                eResult = eARR_SUCCESS;
                break;
            }
            case eARS_FAILURE:
            case eARS_FAILURE_INVALID_OBJECT_ID:
            case eARS_FAILURE_INVALID_CONTROL_ID:
            case eARS_FAILURE_INVALID_REQUEST:
            case eARS_PARTIAL_SUCCESS:
            {
                eResult = eARR_FAILURE;
                break;
            }
            default:
            {
                g_audioLogger.Log(eALT_ERROR, "Invalid AudioRequestStatus '%u'. Cannot be converted to an AudioRequestResult. ", eAudioRequestStatus);
                AZ_Assert(false, "Invalid AudioRequestStatus '%u'.  Cannot be converted to and AudioRequestResult.", eAudioRequestStatus);
                eResult = eARR_FAILURE;
                break;
            }
        }

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    CAudioTranslationLayer::CAudioTranslationLayer()
        : m_pGlobalAudioObject(nullptr)
        , m_nGlobalAudioObjectID(GLOBAL_AUDIO_OBJECT_ID)
        , m_nTriggerInstanceIDCounter(1)
        , m_nextSourceId(INVALID_AUDIO_SOURCE_ID)
        , m_oAudioEventMgr()
        , m_oAudioObjectMgr(m_oAudioEventMgr)
        , m_oAudioListenerMgr()
        , m_oFileCacheMgr(m_cPreloadRequests)
        , m_oAudioEventListenerMgr()
        , m_oXmlProcessor(m_cTriggers, m_cRtpcs, m_cSwitches, m_cEnvironments, m_cPreloadRequests, m_oFileCacheMgr)
        , m_nFlags(eAIS_NONE)
    {
        gEnv->pSystem->GetISystemEventDispatcher()->RegisterListener(this);

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        m_oAudioEventMgr.SetDebugNameStore(&m_oDebugNameStore);
        m_oAudioObjectMgr.SetDebugNameStore(&m_oDebugNameStore);
        m_oXmlProcessor.SetDebugNameStore(&m_oDebugNameStore);
#endif // INCLUDE_AUDIO_PRODUCTION_CODE
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    CAudioTranslationLayer::~CAudioTranslationLayer()
    {
        // By the time ATL is being destroyed, ReleaseImplComponent should have been called already
        // to release the implementation object.  See CAudioSystem::Release().

        gEnv->pSystem->GetISystemEventDispatcher()->RemoveListener(this);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::Initialize()
    {
#if AUDIO_ENABLE_CRY_PHYSICS
        // Add the callback for the obstruction calculation.
        gEnv->pPhysicalWorld->AddEventClient(
            EventPhysRWIResult::id,
            &CPropagationProcessor::OnObstructionTest,
            1);
#endif // AUDIO_ENABLE_CRY_PHYSICS

        m_lastUpdateTime = AZStd::chrono::system_clock::now();
        return true;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::ShutDown()
    {
#if AUDIO_ENABLE_CRY_PHYSICS
        if (gEnv->pPhysicalWorld)
        {
            // remove the callback for the obstruction calculation
            gEnv->pPhysicalWorld->RemoveEventClient(
                EventPhysRWIResult::id,
                &CPropagationProcessor::OnObstructionTest,
                1);
        }
#endif // AUDIO_ENABLE_CRY_PHYSICS

        return true;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::ProcessRequest(CAudioRequestInternal& rRequest)
    {
        EAudioRequestStatus eResult = eARS_NONE;

        switch (rRequest.pData->eRequestType)
        {
            case eART_AUDIO_OBJECT_REQUEST:
            {
                eResult = ProcessAudioObjectRequest(rRequest);
                break;
            }
            case eART_AUDIO_LISTENER_REQUEST:
            {
                eResult = ProcessAudioListenerRequest(rRequest);
                break;
            }
            case eART_AUDIO_CALLBACK_MANAGER_REQUEST:
            {
                eResult = ProcessAudioCallbackManagerRequest(rRequest.pData);
                break;
            }
            case eART_AUDIO_MANAGER_REQUEST:
            {
                eResult = ProcessAudioManagerRequest(rRequest);
                break;
            }
            default:
            {
                g_audioLogger.Log(eALT_ERROR, "Unknown audio request type: %d", static_cast<int>(rRequest.pData->eRequestType));
                break;
            }
        }

        rRequest.eStatus = eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::Update()
    {
        AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Audio);

        auto current = AZStd::chrono::system_clock::now();
        m_elapsedTime = AZStd::chrono::duration_cast<duration_ms>(current - m_lastUpdateTime);
        m_lastUpdateTime = current;
        float elapsedMs = m_elapsedTime.count();

        UpdateSharedData();

        m_oAudioEventMgr.Update(elapsedMs);
        m_oAudioObjectMgr.Update(elapsedMs, m_oSharedData.m_oActiveListenerPosition);
        m_oAudioListenerMgr.Update(elapsedMs);
        m_oFileCacheMgr.Update();

        AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::Update, elapsedMs);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioControlID CAudioTranslationLayer::GetAudioTriggerID(const char* const audioTriggerName) const
    {
        auto triggerId = AudioStringToID<TAudioControlID>(audioTriggerName);
        auto it = m_cTriggers.find(triggerId);
        if (it == m_cTriggers.end())
        {
            return INVALID_AUDIO_CONTROL_ID;
        }

        return triggerId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioControlID CAudioTranslationLayer::GetAudioRtpcID(const char* const audioRtpcName) const
    {
        auto rtpcId = AudioStringToID<TAudioControlID>(audioRtpcName);
        auto it = m_cRtpcs.find(rtpcId);
        if (it == m_cRtpcs.end())
        {
            return INVALID_AUDIO_CONTROL_ID;
        }

        return rtpcId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioControlID CAudioTranslationLayer::GetAudioSwitchID(const char* const audioStateName) const
    {
        auto switchId = AudioStringToID<TAudioControlID>(audioStateName);
        auto it = m_cSwitches.find(switchId);
        if (it == m_cSwitches.end())
        {
            return INVALID_AUDIO_CONTROL_ID;
        }

        return switchId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioSwitchStateID CAudioTranslationLayer::GetAudioSwitchStateID(const TAudioControlID switchId, const char* const audioSwitchStateName) const
    {
        auto stateId = AudioStringToID<TAudioSwitchStateID>(audioSwitchStateName);
        auto itSwitch = m_cSwitches.find(switchId);
        if (itSwitch != m_cSwitches.end())
        {
            auto itState = itSwitch->second->cStates.find(stateId);
            if (itState == itSwitch->second->cStates.end())
            {
                return INVALID_AUDIO_SWITCH_STATE_ID;
            }
        }

        return stateId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioPreloadRequestID CAudioTranslationLayer::GetAudioPreloadRequestID(const char* const audioPreloadRequestName) const
    {
        auto preloadRequestId = AudioStringToID<TAudioPreloadRequestID>(audioPreloadRequestName);
        auto it = m_cPreloadRequests.find(preloadRequestId);
        if (it == m_cPreloadRequests.end())
        {
            return INVALID_AUDIO_PRELOAD_REQUEST_ID;
        }

        return preloadRequestId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioEnvironmentID CAudioTranslationLayer::GetAudioEnvironmentID(const char* const audioEnvironmentName) const
    {
        auto environmentId = AudioStringToID<TAudioEnvironmentID>(audioEnvironmentName);
        auto it = m_cEnvironments.find(environmentId);
        if (it == m_cEnvironments.end())
        {
            return INVALID_AUDIO_ENVIRONMENT_ID;
        }

        return environmentId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::ReserveAudioObjectID(TAudioObjectID& rAudioObjectID)
    {
        return m_oAudioObjectMgr.ReserveID(rAudioObjectID);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::ReleaseAudioObjectID(const TAudioObjectID nAudioObjectID)
    {
        const bool bSuccess = m_oAudioObjectMgr.ReleaseID(nAudioObjectID);

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        if (bSuccess)
        {
            m_oDebugNameStore.RemoveAudioObject(nAudioObjectID);
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return bSuccess;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::ReserveAudioListenerID(TAudioObjectID& rAudioObjectID)
    {
        return m_oAudioListenerMgr.ReserveID(rAudioObjectID);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::ReleaseAudioListenerID(const TAudioObjectID nAudioObjectID)
    {
        return m_oAudioListenerMgr.ReleaseID(nAudioObjectID);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::SetAudioListenerOverrideID(const TAudioObjectID nAudioObjectID)
    {
        return m_oAudioListenerMgr.SetOverrideListenerID(nAudioObjectID);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::AddRequestListener(const SAudioEventListener& listener)
    {
        m_oAudioEventListenerMgr.AddRequestListener(listener);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::RemoveRequestListener(const SAudioEventListener& listener)
    {
        m_oAudioEventListenerMgr.RemoveRequestListener(listener);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ParseControlsData(const char* const pConfigFolderPath, const EATLDataScope eDataScope)
    {
        m_oXmlProcessor.ParseControlsData(pConfigFolderPath, eDataScope);
        return eARS_SUCCESS;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ParsePreloadsData(const char* const pConfigFolderPath, const EATLDataScope eDataScope)
    {
        m_oXmlProcessor.ParsePreloadsData(pConfigFolderPath, eDataScope);
        return eARS_SUCCESS;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ClearControlsData(const EATLDataScope eDataScope)
    {
        m_oXmlProcessor.ClearControlsData(eDataScope);
        return eARS_SUCCESS;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ClearPreloadsData(const EATLDataScope eDataScope)
    {
        m_oXmlProcessor.ClearPreloadsData(eDataScope);
        return eARS_SUCCESS;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    const AZStd::string& CAudioTranslationLayer::GetControlsImplSubPath() const
    {
        return m_implSubPath;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    TAudioSourceId CAudioTranslationLayer::CreateAudioSource(const SAudioInputConfig& sourceConfig)
    {
        AZ_Assert(sourceConfig.m_sourceId == INVALID_AUDIO_SOURCE_ID, "ATL - Request to CreateAudioSource already contains a valid source Id.\n");

        TAudioSourceId sourceId = ++m_nextSourceId;
        SAudioRequest request;
        SAudioManagerRequestData<eAMRT_CREATE_SOURCE> requestData(sourceConfig);
        requestData.m_sourceConfig.m_sourceId = sourceId;
        request.nFlags = (eARF_PRIORITY_HIGH | eARF_EXECUTE_BLOCKING);
        request.pData = &requestData;

        AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequestBlocking, request);
        return sourceId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::DestroyAudioSource(TAudioSourceId sourceId)
    {
        SAudioRequest request;
        SAudioManagerRequestData<eAMRT_DESTROY_SOURCE> requestData(sourceId);
        request.nFlags = (eARF_PRIORITY_NORMAL);
        request.pData = &requestData;

        AudioSystemRequestBus::Broadcast(&AudioSystemRequestBus::Events::PushRequest, request);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::NotifyListener(const CAudioRequestInternal& rRequest)
    {
        // This should always be the main thread!
        TATLEnumFlagsType nSpecificAudioRequest = INVALID_AUDIO_ENUM_FLAG_TYPE;
        TAudioControlID nAudioControlID = INVALID_AUDIO_CONTROL_ID;
        TAudioEventID audioEventID = INVALID_AUDIO_EVENT_ID;

        switch (rRequest.pData->eRequestType)
        {
            case eART_AUDIO_MANAGER_REQUEST:
            {
                auto const pRequestDataBase = static_cast<const SAudioManagerRequestDataInternalBase*>(rRequest.pData.get());
                nSpecificAudioRequest = static_cast<TATLEnumFlagsType>(pRequestDataBase->eType);
                break;
            }
            case eART_AUDIO_CALLBACK_MANAGER_REQUEST:
            {
                auto const pRequestDataBase = static_cast<const SAudioCallbackManagerRequestDataInternalBase*>(rRequest.pData.get());
                nSpecificAudioRequest = static_cast<TATLEnumFlagsType>(pRequestDataBase->eType);
                switch (pRequestDataBase->eType)
                {
                    case eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE:
                    {
                        auto const pRequestData = static_cast<const SAudioCallbackManagerRequestDataInternal<eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE>*>(pRequestDataBase);
                        nAudioControlID = pRequestData->nAudioTriggerID;
                        break;
                    }
                    case eACMRT_REPORT_STARTED_EVENT:
                    {
                        auto const pRequestData = static_cast<const SAudioCallbackManagerRequestDataInternal<eACMRT_REPORT_STARTED_EVENT>*>(pRequestDataBase);
                        audioEventID = pRequestData->nEventID;
                        break;
                    }
                }
                break;
            }
            case eART_AUDIO_OBJECT_REQUEST:
            {
                auto const pRequestDataBase = static_cast<const SAudioObjectRequestDataInternalBase*>(rRequest.pData.get());
                nSpecificAudioRequest = static_cast<TATLEnumFlagsType>(pRequestDataBase->eType);

                switch (pRequestDataBase->eType)
                {
                    case eAORT_EXECUTE_TRIGGER:
                    {
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_EXECUTE_TRIGGER>*>(pRequestDataBase);
                        nAudioControlID = pRequestData->nTriggerID;
                        break;
                    }
                }
                break;
            }
            case eART_AUDIO_LISTENER_REQUEST:
            {
                auto const pRequestDataBase = static_cast<const SAudioListenerRequestDataInternalBase*>(rRequest.pData.get());
                nSpecificAudioRequest = static_cast<TATLEnumFlagsType>(pRequestDataBase->eType);
                break;
            }
            default:
            {
                g_audioLogger.Log(eALT_ASSERT, "Unknown request type during CAudioTranslationLayer::NotifyListener!");
                break;
            }
        }

        const SAudioRequestInfo oResult(
            ConvertToRequestResult(rRequest.eStatus),
            rRequest.pOwner,
            rRequest.pUserData,
            rRequest.pUserDataOwner,
            rRequest.pData->eRequestType,
            nSpecificAudioRequest,
            nAudioControlID,
            rRequest.nAudioObjectID,
            audioEventID);

        m_oAudioEventListenerMgr.NotifyListener(&oResult);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ProcessAudioManagerRequest(const CAudioRequestInternal& rRequest)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        if (rRequest.pData)
        {
            auto const pRequestDataBase = static_cast<const SAudioManagerRequestDataInternalBase*>(rRequest.pData.get());

            switch (pRequestDataBase->eType)
            {
                case eAMRT_RESERVE_AUDIO_OBJECT_ID:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_RESERVE_AUDIO_OBJECT_ID>*>(rRequest.pData.get());
                #if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
                    eResult = BoolToARS(ReserveAudioObjectID(*pRequestData->pObjectID, pRequestData->sObjectName.c_str()));
                #else
                    eResult = BoolToARS(ReserveAudioObjectID(*pRequestData->pObjectID));
                #endif // INCLUDE_AUDIO_PRODUCTION_CODE
                    break;
                }
                case eAMRT_CREATE_SOURCE:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_CREATE_SOURCE>*>(rRequest.pData.get());
                    bool result = false;
                    AudioSystemImplementationRequestBus::BroadcastResult(result, &AudioSystemImplementationRequestBus::Events::CreateAudioSource, pRequestData->m_sourceConfig);
                    eResult = BoolToARS(result);
                    break;
                }
                case eAMRT_DESTROY_SOURCE:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_DESTROY_SOURCE>*>(rRequest.pData.get());
                    AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::DestroyAudioSource, pRequestData->m_sourceId);
                    eResult = eARS_SUCCESS;
                    break;
                }
                case eAMRT_INIT_AUDIO_IMPL:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_INIT_AUDIO_IMPL>*>(rRequest.pData.get());
                    eResult = InitializeImplComponent();

                    // Initializing the implementation failed, immediately release it...
                    if (eResult != eARS_SUCCESS)
                    {
                        ReleaseImplComponent();
                    }
                    break;
                }
                case eAMRT_RELEASE_AUDIO_IMPL:
                {
                    ReleaseImplComponent();
                    eResult = eARS_SUCCESS;
                    break;
                }
                case eAMRT_REFRESH_AUDIO_SYSTEM:
                {
                #if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_REFRESH_AUDIO_SYSTEM>*>(rRequest.pData.get());
                    eResult = RefreshAudioSystem(pRequestData->m_controlsPath.c_str(), pRequestData->m_levelName.c_str(), pRequestData->m_levelPreloadId);
                #else
                    eResult = eARS_FAILURE_INVALID_REQUEST;
                #endif // INCLUDE_AUDIO_PRODUCTION_CODE
                    break;
                }
                case eAMRT_LOSE_FOCUS:
                {
                #if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
                    if (g_audioCVars.m_nIgnoreWindowFocus == 0 && (m_nFlags & eAIS_IS_MUTED) == 0)
                #endif // INCLUDE_AUDIO_PRODUCTION_CODE
                    {
                        auto it = m_cTriggers.find(ATLInternalControlIDs::LoseFocusTriggerID);
                        if (it != m_cTriggers.end())
                        {
                            eResult = ActivateTrigger(m_pGlobalAudioObject, it->second, 0.0f);
                        }
                        else
                        {
                            g_audioLogger.Log(eALT_WARNING, "ATL - Trigger not found for: ATLInternalControlIDs::LoseFocusTriggerID");
                        }

                        AudioSystemImplementationNotificationBus::Broadcast(&AudioSystemImplementationNotificationBus::Events::OnAudioSystemLoseFocus);
                    }
                    break;
                }
                case eAMRT_GET_FOCUS:
                {
                #if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
                    if (g_audioCVars.m_nIgnoreWindowFocus == 0 && (m_nFlags & eAIS_IS_MUTED) == 0)
                #endif // INCLUDE_AUDIO_PRODUCTION_CODE
                    {
                        AudioSystemImplementationNotificationBus::Broadcast(&AudioSystemImplementationNotificationBus::Events::OnAudioSystemGetFocus);

                        auto it = m_cTriggers.find(ATLInternalControlIDs::GetFocusTriggerID);
                        if (it != m_cTriggers.end())
                        {
                            eResult = ActivateTrigger(m_pGlobalAudioObject, it->second, 0.0f);
                        }
                        else
                        {
                            g_audioLogger.Log(eALT_WARNING, "ATL - Trigger not found for: ATLInternalControlIDs::GetFocusTriggerID");
                        }
                    }
                    break;
                }
                case eAMRT_MUTE_ALL:
                {
                    eResult = MuteAll();
                    break;
                }
                case eAMRT_UNMUTE_ALL:
                {
                    eResult = UnmuteAll();
                    break;
                }
                case eAMRT_STOP_ALL_SOUNDS:
                {
                    AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::StopAllSounds);
                    break;
                }
                case eAMRT_PARSE_CONTROLS_DATA:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_PARSE_CONTROLS_DATA>*>(rRequest.pData.get());
                    eResult = ParseControlsData(pRequestData->sControlsPath.c_str(), pRequestData->eDataScope);
                    break;
                }
                case eAMRT_PARSE_PRELOADS_DATA:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_PARSE_PRELOADS_DATA>*>(rRequest.pData.get());
                    eResult = ParsePreloadsData(pRequestData->sControlsPath.c_str(), pRequestData->eDataScope);
                    break;
                }
                case eAMRT_CLEAR_CONTROLS_DATA:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_CLEAR_CONTROLS_DATA>*>(rRequest.pData.get());
                    eResult = ClearControlsData(pRequestData->eDataScope);
                    break;
                }
                case eAMRT_CLEAR_PRELOADS_DATA:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_CLEAR_PRELOADS_DATA>*>(rRequest.pData.get());
                    eResult = ClearPreloadsData(pRequestData->eDataScope);
                    break;
                }
                case eAMRT_PRELOAD_SINGLE_REQUEST:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_PRELOAD_SINGLE_REQUEST>*>(rRequest.pData.get());
                    eResult = m_oFileCacheMgr.TryLoadRequest(pRequestData->nPreloadRequest, ((rRequest.nFlags & eARF_EXECUTE_BLOCKING) != 0), pRequestData->bAutoLoadOnly);
                    break;
                }
                case eAMRT_UNLOAD_SINGLE_REQUEST:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_UNLOAD_SINGLE_REQUEST>*>(rRequest.pData.get());
                    eResult = m_oFileCacheMgr.TryUnloadRequest(pRequestData->nPreloadRequest);
                    break;
                }
                case eAMRT_UNLOAD_AFCM_DATA_BY_SCOPE:
                {
                    auto const pRequestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_UNLOAD_AFCM_DATA_BY_SCOPE>*>(rRequest.pData.get());
                    eResult = m_oFileCacheMgr.UnloadDataByScope(pRequestData->eDataScope);
                    break;
                }
                case eAMRT_CHANGE_LANGUAGE:
                {
                    SetImplLanguage();

                    m_oFileCacheMgr.UpdateLocalizedFileCacheEntries();
                    eResult = eARS_SUCCESS;
                    break;
                }
                case eAMRT_SET_AUDIO_PANNING_MODE:
                {
                    auto const requestData = static_cast<const SAudioManagerRequestDataInternal<eAMRT_SET_AUDIO_PANNING_MODE>*>(rRequest.pData.get());
                    AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::SetPanningMode, requestData->m_panningMode);
                    eResult = eARS_SUCCESS;
                    break;
                }
                case eAMRT_DRAW_DEBUG_INFO:
                {
                #if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
                    DrawAudioSystemDebugInfo();
                    eResult = eARS_SUCCESS;
                #endif // INCLUDE_AUDIO_PRODUCTION_CODE
                    break;
                }
                case eAMRT_NONE:
                {
                    eResult = eARS_SUCCESS;
                    break;
                }
                default:
                {
                    g_audioLogger.Log(eALT_WARNING, "ATL received an unknown AudioManager request: %u", pRequestDataBase->eType);
                    eResult = eARS_FAILURE_INVALID_REQUEST;
                    break;
                }
            }
        }

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ProcessAudioCallbackManagerRequest(const SAudioRequestDataInternal* pPassedRequestData)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        if (pPassedRequestData)
        {
            auto const pRequestDataBase = static_cast<const SAudioCallbackManagerRequestDataInternalBase*>(pPassedRequestData);

            switch (pRequestDataBase->eType)
            {
                case eACMRT_REPORT_STARTED_EVENT:
                {
                    auto const pRequestData = static_cast<const SAudioCallbackManagerRequestDataInternal<eACMRT_REPORT_STARTED_EVENT>*>(pPassedRequestData);
                    CATLEvent* const pEvent = m_oAudioEventMgr.LookupID(pRequestData->nEventID);

                    if (pEvent)
                    {
                        pEvent->m_audioEventState = eAES_PLAYING_DELAYED;

                        if (pEvent->m_nObjectID != m_nGlobalAudioObjectID)
                        {
                            m_oAudioObjectMgr.ReportStartedEvent(pEvent);
                        }
                        else
                        {
                            m_pGlobalAudioObject->ReportStartedEvent(pEvent);
                        }
                    }

                    eResult = eARS_SUCCESS;
                    break;
                }
                case eACMRT_REPORT_FINISHED_EVENT:
                {
                    auto const pRequestData = static_cast<const SAudioCallbackManagerRequestDataInternal<eACMRT_REPORT_FINISHED_EVENT>*>(pPassedRequestData);
                    CATLEvent* const pEvent = m_oAudioEventMgr.LookupID(pRequestData->nEventID);

                    if (pEvent)
                    {
                        if (pEvent->m_nObjectID != m_nGlobalAudioObjectID)
                        {
                            m_oAudioObjectMgr.ReportFinishedEvent(pEvent, pRequestData->bSuccess);
                        }
                        else if (m_pGlobalAudioObject)
                        {   // safety-net check to prevent crash if a finished callback is received very late
                            m_pGlobalAudioObject->ReportFinishedEvent(pEvent, pRequestData->bSuccess);
                        }

                        m_oAudioEventMgr.ReleaseEvent(pEvent);
                    }

                    eResult = eARS_SUCCESS;
                    break;
                }
#if AUDIO_ENABLE_CRY_PHYSICS
                case eACMRT_REPORT_PROCESSED_OBSTRUCTION_RAY:
                {
                    auto const pRequestData = static_cast<const SAudioCallbackManagerRequestDataInternal<eACMRT_REPORT_PROCESSED_OBSTRUCTION_RAY>*>(pPassedRequestData);
                    m_oAudioObjectMgr.ReportObstructionRay(pRequestData->nObjectID, pRequestData->nRayID);

                    eResult = eARS_SUCCESS;
                    break;
                }
#endif // AUDIO_ENABLE_CRY_PHYSICS
                case eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE:
                case eACMRT_NONE:
                {
                    eResult = eARS_SUCCESS;
                    break;
                }
                default:
                {
                    eResult = eARS_FAILURE_INVALID_REQUEST;
                    g_audioLogger.Log(eALT_WARNING, "ATL received an unknown AudioCallbackManager request: %u", pRequestDataBase->eType);
                    break;
                }
            }
        }

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ProcessAudioObjectRequest(const CAudioRequestInternal& rRequest)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;
        CATLAudioObjectBase* pObject = static_cast<CATLAudioObjectBase*>(m_pGlobalAudioObject);

        if (rRequest.nAudioObjectID != INVALID_AUDIO_OBJECT_ID)
        {
            pObject = static_cast<CATLAudioObjectBase*>(m_oAudioObjectMgr.LookupID(rRequest.nAudioObjectID));
        }

        if (pObject)
        {
            const SAudioRequestDataInternal* const pPassedRequestData = rRequest.pData;

            if (pPassedRequestData)
            {
                auto const pBaseRequestData = static_cast<const SAudioObjectRequestDataInternalBase*>(pPassedRequestData);

                switch (pBaseRequestData->eType)
                {
                    case eAORT_PREPARE_TRIGGER:
                    {
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_PREPARE_TRIGGER>*>(pPassedRequestData);

                        auto it = m_cTriggers.find(pRequestData->nTriggerID);
                        if (it != m_cTriggers.end())
                        {
                            eResult = PrepUnprepTriggerAsync(pObject, it->second, true);
                        }
                        else
                        {
                            eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                        }
                        break;
                    }
                    case eAORT_UNPREPARE_TRIGGER:
                    {
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_UNPREPARE_TRIGGER>*>(pPassedRequestData);

                        auto it = m_cTriggers.find(pRequestData->nTriggerID);
                        if (it != m_cTriggers.end())
                        {
                            eResult = PrepUnprepTriggerAsync(pObject, it->second, false);
                        }
                        else
                        {
                            eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                        }
                        break;
                    }
                    case eAORT_EXECUTE_TRIGGER:
                    {
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_EXECUTE_TRIGGER>*>(pPassedRequestData);

                        auto it = m_cTriggers.find(pRequestData->nTriggerID);
                        if (it != m_cTriggers.end())
                        {
                            eResult = ActivateTrigger(
                                pObject,
                                it->second,
                                pRequestData->fTimeUntilRemovalInMS,
                                rRequest.pOwner,
                                rRequest.pUserData,
                                rRequest.pUserDataOwner,
                                rRequest.nFlags);
                        }
                        else
                        {
                            eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                        }
                        break;
                    }
                    case eAORT_STOP_TRIGGER:
                    {
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_STOP_TRIGGER>*>(pPassedRequestData);

                        auto it = m_cTriggers.find(pRequestData->nTriggerID);
                        if (it != m_cTriggers.end())
                        {
                            eResult = StopTrigger(pObject, it->second);
                        }
                        else
                        {
                            eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                        }
                        break;
                    }
                    case eAORT_STOP_ALL_TRIGGERS:
                    {
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_STOP_ALL_TRIGGERS>*>(pPassedRequestData);

                        if (pRequestData->m_filterByOwner)
                        {
                            StopAllTriggers(pObject, rRequest.pOwner);
                        }
                        else
                        {
                            StopAllTriggers(pObject);
                        }

                        // Why not return the result of StopAllTriggers call?
                        eResult = eARS_SUCCESS;
                        break;
                    }
                    case eAORT_SET_POSITION:
                    {
                        if (pObject->HasPosition())
                        {
                            auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_SET_POSITION>*>(pPassedRequestData);

                            auto const pPositionedObject = static_cast<CATLAudioObject*>(pObject);

                            AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::SetPosition,
                                pPositionedObject->GetImplDataPtr(),
                                pRequestData->oPosition);

                            if (eResult == eARS_SUCCESS)
                            {
                                pPositionedObject->SetPosition(pRequestData->oPosition);
                            }
                        }
                        else
                        {
                            g_audioLogger.Log(eALT_WARNING, "ATL received a request to set a position on a global object");
                        }
                        break;
                    }
                    case eAORT_SET_RTPC_VALUE:
                    {
                        eResult = eARS_FAILURE_INVALID_CONTROL_ID;

                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_SET_RTPC_VALUE>*>(pPassedRequestData);

                        auto it = m_cRtpcs.find(pRequestData->nControlID);
                        if (it != m_cRtpcs.end())
                        {
                            eResult = SetRtpc(pObject, it->second, pRequestData->fValue);
                        }
                        break;
                    }
                    case eAORT_SET_SWITCH_STATE:
                    {
                        eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                        auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_SET_SWITCH_STATE>*>(pPassedRequestData);

                        auto itSwitch = m_cSwitches.find(pRequestData->nSwitchID);
                        if (itSwitch != m_cSwitches.end())
                        {
                            auto itState = itSwitch->second->cStates.find(pRequestData->nStateID);
                            if (itState != itSwitch->second->cStates.end())
                            {
                                eResult = SetSwitchState(pObject, itState->second);
                            }
                        }
                        break;
                    }
                    case eAORT_SET_ENVIRONMENT_AMOUNT:
                    {
                        if (pObject->HasPosition())
                        {
                            eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                            auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_SET_ENVIRONMENT_AMOUNT>*>(pPassedRequestData);

                            auto it = m_cEnvironments.find(pRequestData->nEnvironmentID);
                            if (it != m_cEnvironments.end())
                            {
                                eResult = SetEnvironment(pObject, it->second, pRequestData->fAmount);
                            }
                        }
                        else
                        {
                            g_audioLogger.Log(eALT_WARNING, "ATL received a request to set an environment on a global object");
                        }
                        break;
                    }
                    case eAORT_RESET_ENVIRONMENTS:
                    {
                        eResult = ResetEnvironments(pObject);
                        break;
                    }
                    case eAORT_RESET_RTPCS:
                    {
                        eResult = ResetRtpcs(pObject);
                        break;
                    }
                    case eAORT_RELEASE_OBJECT:
                    {
                        eResult = eARS_FAILURE;

                        const TAudioObjectID nObjectID = pObject->GetID();

                        if (nObjectID != m_nGlobalAudioObjectID)
                        {
                            if (ReleaseAudioObjectID(nObjectID))
                            {
                                eResult = eARS_SUCCESS;
                            }
                        }
                        else
                        {
                            g_audioLogger.Log(eALT_WARNING, "ATL received a request to release the GlobalAudioObject");
                        }
                        break;
                    }
                    case eAORT_EXECUTE_SOURCE_TRIGGER:
                    {
                        eResult = eARS_FAILURE;
                        auto const requestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_EXECUTE_SOURCE_TRIGGER>*>(pPassedRequestData);

                        auto it = m_cTriggers.find(requestData->m_triggerId);
                        if (it != m_cTriggers.end())
                        {
                            SATLSourceData sourceData(requestData->m_sourceInfo);
                            eResult = ActivateTrigger(
                                pObject,
                                it->second,
                                0.f,
                                rRequest.pOwner,
                                rRequest.pUserData,
                                rRequest.pUserDataOwner,
                                rRequest.nFlags,
                                &sourceData
                            );
                        }
                        else
                        {
                            eResult = eARS_FAILURE_INVALID_CONTROL_ID;
                        }

                        break;
                    }
                    case eAORT_SET_MULTI_POSITIONS:
                    {
                        if (pObject->HasPosition())
                        {
                            auto const pRequestData = static_cast<const SAudioObjectRequestDataInternal<eAORT_SET_MULTI_POSITIONS>*>(pPassedRequestData);

                            auto const pPositionedObject = static_cast<CATLAudioObject*>(pObject);

                            AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::SetMultiplePositions,
                                pPositionedObject->GetImplDataPtr(),
                                pRequestData->m_params);

                            if (eResult == eARS_SUCCESS)
                            {
                                pPositionedObject->SetPosition(SATLWorldPosition());
                            }
                        }
                        else
                        {
                            g_audioLogger.Log(eALT_WARNING, "ATL received a request to set multiple positions on a global object");
                        }
                        break;
                    }
                    case eAORT_NONE:
                    {
                        eResult = eARS_SUCCESS;
                        break;
                    }
                    default:
                    {
                        eResult = eARS_FAILURE_INVALID_REQUEST;
                        g_audioLogger.Log(eALT_WARNING, "ATL received an unknown AudioObject request type: %u", pBaseRequestData->eType);
                        break;
                    }
                }
            }
        }
        else
        {
            g_audioLogger.Log(eALT_WARNING, "ATL received a request to a non-existent AudioObject: %u", rRequest.nAudioObjectID);
            eResult = eARS_FAILURE_INVALID_OBJECT_ID;
        }

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ProcessAudioListenerRequest(const CAudioRequestInternal& rRequest)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;
        TAudioObjectID listenerID = INVALID_AUDIO_OBJECT_ID;

        // Check for an Audio Listener Override.
        TAudioObjectID overrideListenerID = m_oAudioListenerMgr.GetOverrideListenerID();
        if (overrideListenerID != INVALID_AUDIO_OBJECT_ID)
        {
            if (rRequest.nAudioObjectID == overrideListenerID)
            {
                // Have an override set, and the ID in the request matches the override.
                // Reroute to the default listener.
                listenerID = m_oAudioListenerMgr.GetDefaultListenerID();
            }
            else if (rRequest.nAudioObjectID != INVALID_AUDIO_OBJECT_ID)
            {
                // Override is set, but the request specified a different listener ID, allow it.
                listenerID = rRequest.nAudioObjectID;
            }
            else
            {
                // Override is set, but no listener ID specified.  Typically this would go
                // to the default listener, but with overrides we explicitly ignore this.
                return eResult;
            }
        }
        else if (rRequest.nAudioObjectID == INVALID_AUDIO_OBJECT_ID)
        {
            listenerID = m_oAudioListenerMgr.GetDefaultListenerID();
        }
        else
        {
            listenerID = rRequest.nAudioObjectID;
        }

        CATLListenerObject* const pListener = m_oAudioListenerMgr.LookupID(listenerID);
        if (pListener)
        {
            if (rRequest.pData)
            {
                auto const pBaseRequestData = static_cast<const SAudioListenerRequestDataInternalBase*>(rRequest.pData.get());

                switch (pBaseRequestData->eType)
                {
                    case eALRT_SET_POSITION:
                    {
                        auto const pRequestData = static_cast<const SAudioListenerRequestDataInternal<eALRT_SET_POSITION>*>(rRequest.pData.get());

                        AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::SetListenerPosition,
                            pListener->m_pImplData,
                            pRequestData->oNewPosition);

                        if (eResult == eARS_SUCCESS)
                        {
                            pListener->oPosition = pRequestData->oNewPosition;
                        }
                        break;
                    }
                    case eALRT_NONE:
                    {
                        eResult = eARS_SUCCESS;
                        break;
                    }
                    default:
                    {
                        eResult = eARS_FAILURE_INVALID_REQUEST;
                        g_audioLogger.Log(eALT_WARNING, "ATL received an unknown AudioListener request type: %u", pBaseRequestData->eType);
                        break;
                    }
                }
            }
        }
        else
        {
            g_audioLogger.Log(eALT_COMMENT, "Could not find listener with ID: %u", listenerID);
        }

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::InitializeImplComponent()
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::Initialize);
        if (eResult == eARS_SUCCESS)
        {
            IATLAudioObjectData* pGlobalObjectData = nullptr;
            AudioSystemImplementationRequestBus::BroadcastResult(pGlobalObjectData, &AudioSystemImplementationRequestBus::Events::NewGlobalAudioObjectData, m_nGlobalAudioObjectID);

            m_pGlobalAudioObject = azcreate(CATLGlobalAudioObject, (m_nGlobalAudioObjectID, pGlobalObjectData), Audio::AudioSystemAllocator, "ATLGlobalAudioObject");

            m_oAudioObjectMgr.Initialize();
            m_oAudioEventMgr.Initialize();
            m_oAudioListenerMgr.Initialize();
            m_oXmlProcessor.Initialize();
            m_oFileCacheMgr.Initialize();

            SetImplLanguage();

            // Update the implementation subpath for locating ATL controls...
            AudioSystemImplementationRequestBus::BroadcastResult(m_implSubPath, &AudioSystemImplementationRequestBus::Events::GetImplSubPath);
        }

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        else
        {
            const char* implementationName = nullptr;
            AudioSystemImplementationRequestBus::BroadcastResult(implementationName, &AudioSystemImplementationRequestBus::Events::GetImplementationNameString);
            g_audioLogger.Log(eALT_ERROR, "Failed to Initialize the AudioSystemImplementationComponent '%s'\n", implementationName);
        }
#endif //INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::ReleaseImplComponent()
    {
        // During audio middleware shutdown we do not allow for any new requests originating from the "dying" audio middleware!
        m_nFlags |= eAIS_AUDIO_MIDDLEWARE_SHUTTING_DOWN;

        m_oXmlProcessor.ClearControlsData(eADS_ALL);
        m_oXmlProcessor.ClearPreloadsData(eADS_ALL);

        if (m_pGlobalAudioObject)
        {
            AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::DeleteAudioObjectData, m_pGlobalAudioObject->GetImplDataPtr());
            azdestroy(m_pGlobalAudioObject, Audio::AudioSystemAllocator);
            m_pGlobalAudioObject = nullptr;
        }

        m_oAudioObjectMgr.Release();
        m_oAudioListenerMgr.Release();
        m_oAudioEventMgr.Release();
        m_oFileCacheMgr.Release();
        m_oXmlProcessor.Release();

        m_implSubPath.clear();

        EAudioRequestStatus eResult = eARS_FAILURE;
        AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::ShutDown);

        // If we allow developers to change the audio implementation module at run-time, these should be at Warning level.
        // If we ever revoke that functionality, these should be promoted to Asserts.
        AZ_Warning("ATL", eResult == eARS_SUCCESS, "ATL ReleaseImplComponent - Shutting down the audio implementation failed!");

        AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::Release);
        AZ_Warning("ATL", eResult == eARS_SUCCESS, "ATL ReleaseImplComponent - Releasing the audio implementation failed!");

        m_nFlags &= ~eAIS_AUDIO_MIDDLEWARE_SHUTTING_DOWN;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::PrepUnprepTriggerAsync(
        CATLAudioObjectBase* const pAudioObject,
        const CATLTrigger* const pTrigger,
        const bool bPrepare)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        const TAudioObjectID nATLObjectID = pAudioObject->GetID();
        const TAudioControlID nATLTriggerID = pTrigger->GetID();
        const TObjectTriggerImplStates& rTriggerImplStates = pAudioObject->GetTriggerImpls();

        for (auto const triggerImpl : pTrigger->m_cImplPtrs)
        {
            TATLEnumFlagsType nTriggerImplFlags = INVALID_AUDIO_ENUM_FLAG_TYPE;
            TObjectTriggerImplStates::const_iterator iPlace = rTriggerImplStates.end();
            if (FindPlaceConst(rTriggerImplStates, triggerImpl->m_nATLID, iPlace))
            {
                nTriggerImplFlags = iPlace->second.nFlags;
            }

            const EATLSubsystem eReceiver = triggerImpl->GetReceiver();
            CATLEvent* pEvent = m_oAudioEventMgr.GetEvent(eReceiver);

            EAudioRequestStatus ePrepUnprepResult = eARS_FAILURE;

            switch (eReceiver)
            {
                case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                {
                    if (bPrepare)
                    {
                        if (((nTriggerImplFlags & eATS_PREPARED) == 0) && ((nTriggerImplFlags & eATS_LOADING) == 0))
                        {
                            AudioSystemImplementationRequestBus::BroadcastResult(ePrepUnprepResult, &AudioSystemImplementationRequestBus::Events::PrepareTriggerAsync,
                                pAudioObject->GetImplDataPtr(),
                                triggerImpl->m_pImplData,
                                pEvent->m_pImplData);
                        }
                    }
                    else
                    {
                        if (((nTriggerImplFlags & eATS_PREPARED) != 0) && ((nTriggerImplFlags & eATS_UNLOADING) == 0))
                        {
                            AudioSystemImplementationRequestBus::BroadcastResult(ePrepUnprepResult, &AudioSystemImplementationRequestBus::Events::UnprepareTriggerAsync,
                                pAudioObject->GetImplDataPtr(),
                                triggerImpl->m_pImplData,
                                pEvent->m_pImplData);
                        }
                    }

                    if (ePrepUnprepResult ==  eARS_SUCCESS)
                    {
                        pEvent->m_nObjectID = nATLObjectID;
                        pEvent->m_nTriggerID = triggerImpl->m_nATLID;
                        pEvent->m_nTriggerImplID = triggerImpl->m_nATLID;

                        pEvent->m_audioEventState = bPrepare ? eAES_LOADING : eAES_UNLOADING;
                    }
                    break;
                }
                case eAS_ATL_INTERNAL:
                {
                    //TODO: handle this
                    break;
                }
                default:
                {
                    g_audioLogger.Log(eALT_ERROR, "Unknown ATL Recipient");
                    break;
                }
            }

            if (ePrepUnprepResult == eARS_SUCCESS)
            {
                pEvent->SetDataScope(pTrigger->GetDataScope());
                pAudioObject->ReportStartedEvent(pEvent);
                pAudioObject->IncrementRefCount();
                eResult = eARS_SUCCESS; // if at least one event fires, it is a success
            }
            else
            {
                m_oAudioEventMgr.ReleaseEvent(pEvent);
            }
        }

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        if (eResult != eARS_SUCCESS)
        {
            // No TriggerImpl produced an active event.
            g_audioLogger.Log(eALT_WARNING, "PrepUnprepTriggerAsync failed on AudioObject \"%s\" (ID: %u)", m_oDebugNameStore.LookupAudioObjectName(nATLObjectID), nATLObjectID);
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ActivateTrigger(
        CATLAudioObjectBase* const pAudioObject,
        const CATLTrigger* const pTrigger,
        const float fTimeUntilRemovalMS,
        void* const pOwner /* = nullptr */,
        void* const pUserData /* = nullptr */,
        void* const pUserDataOwner /* = nullptr */,
        const TATLEnumFlagsType nFlags /* = INVALID_AUDIO_ENUM_FLAG_TYPE */,
        const SATLSourceData* pSourceData /* = nullptr */)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        if (pAudioObject->HasPosition())
        {
            // If the AudioObject uses Obstruction/Occlusion then set the values before activating the trigger.
            auto const pPositionedAudioObject = static_cast<CATLAudioObject*>(pAudioObject);

#if AUDIO_ENABLE_CRY_PHYSICS
            if (pPositionedAudioObject->CanRunObstructionOcclusion() && !pPositionedAudioObject->HasActiveEvents())
            {
                pPositionedAudioObject->ResetObstructionOcclusion(m_oSharedData.m_oActiveListenerPosition);
            }
#else
            if (pPositionedAudioObject->CanRunRaycasts() && !pPositionedAudioObject->HasActiveEvents())
            {
                pPositionedAudioObject->RunRaycasts(m_oSharedData.m_oActiveListenerPosition);
            }
#endif // AUDIO_ENABLE_CRY_PHYSICS
        }

        const TAudioControlID nATLTriggerID = pTrigger->GetID();

        // Sets eATS_STARTING on this TriggerInstance to avoid
        // reporting TriggerFinished while the events are being started.
        pAudioObject->ReportStartingTriggerInstance(m_nTriggerInstanceIDCounter, nATLTriggerID);

        for (auto const triggerImpl : pTrigger->m_cImplPtrs)
        {
            const EATLSubsystem eReceiver = triggerImpl->GetReceiver();
            CATLEvent* const pEvent = m_oAudioEventMgr.GetEvent(eReceiver);
            pEvent->m_pImplData->m_triggerId = nATLTriggerID;

            EAudioRequestStatus eActivateResult = eARS_FAILURE;

            switch (eReceiver)
            {
                case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                {
                    AudioSystemImplementationRequestBus::BroadcastResult(eActivateResult, &AudioSystemImplementationRequestBus::Events::ActivateTrigger,
                        pAudioObject->GetImplDataPtr(),
                        triggerImpl->m_pImplData,
                        pEvent->m_pImplData,
                        pSourceData);
                    break;
                }
                case eAS_ATL_INTERNAL:
                {
                    eActivateResult = ActivateInternalTrigger(
                            pAudioObject,
                            triggerImpl->m_pImplData,
                            pEvent->m_pImplData);
                    break;
                }
                default:
                {
                    g_audioLogger.Log(eALT_ERROR, "Unknown ATL Recipient");
                    break;
                }
            }

            if (eActivateResult == eARS_SUCCESS || eActivateResult == eARS_PENDING)
            {
                pEvent->m_nObjectID = pAudioObject->GetID();
                pEvent->m_nTriggerID = nATLTriggerID;
                pEvent->m_nTriggerImplID = triggerImpl->m_nATLID;
                pEvent->m_nTriggerInstanceID = m_nTriggerInstanceIDCounter;
                pEvent->SetDataScope(pTrigger->GetDataScope());

                if (eActivateResult == eARS_SUCCESS)
                {
                    pEvent->m_audioEventState = eAES_PLAYING;
                }
                else if (eActivateResult == eARS_PENDING)
                {
                    pEvent->m_audioEventState = eAES_LOADING;
                }

                pAudioObject->ReportStartedEvent(pEvent);
                pAudioObject->IncrementRefCount();

                // If at least one event fires, it is a success: the trigger has been activated.
                eResult = eARS_SUCCESS;
            }
            else
            {
                m_oAudioEventMgr.ReleaseEvent(pEvent);
            }
        }

        // Either removes the eATS_STARTING flag on this trigger instance or removes it if no event was started.
        pAudioObject->ReportStartedTriggerInstance(m_nTriggerInstanceIDCounter++, pOwner, pUserData, pUserDataOwner, nFlags);

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        if (eResult != eARS_SUCCESS)
        {
            // No TriggerImpl generated an active event.
            g_audioLogger.Log(eALT_WARNING, "Trigger \"%s\" failed on AudioObject \"%s\" (ID: %u)", m_oDebugNameStore.LookupAudioTriggerName(nATLTriggerID), m_oDebugNameStore.LookupAudioObjectName(pAudioObject->GetID()), pAudioObject->GetID());
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::StopTrigger(
        CATLAudioObjectBase* const pAudioObject,
        const CATLTrigger* const pTrigger)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        const TAudioObjectID nATLObjectID = pAudioObject->GetID();
        const TAudioControlID nATLTriggerID = pTrigger->GetID();

        TObjectEventSet rEvents = pAudioObject->GetActiveEvents();

        for (auto eventId : rEvents)
        {
            const CATLEvent* const pEvent = m_oAudioEventMgr.LookupID(eventId);

            if (pEvent && pEvent->IsPlaying() && (pEvent->m_nTriggerID == nATLTriggerID))
            {
                switch (pEvent->m_eSender)
                {
                    case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                    {
                        AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::StopEvent,
                            pAudioObject->GetImplDataPtr(),
                            pEvent->m_pImplData);
                        break;
                    }
                    case eAS_ATL_INTERNAL:
                    {
                        eResult = StopInternalEvent(pAudioObject, pEvent->m_pImplData);
                        break;
                    }
                    default:
                    {
                        g_audioLogger.Log(eALT_ERROR, "ATL - StopTrigger: Unknown ATL Recipient");
                        break;
                    }
                }
            }
        }

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::StopAllTriggers(CATLAudioObjectBase* const pAudioObject, void* const pOwner /* = nullptr*/)
    {
        if (!pOwner)
        {
            EAudioRequestStatus eResult = eARS_FAILURE;
            AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::StopAllEvents, pAudioObject->GetImplDataPtr());
            return eResult;
        }
        else
        {
            EAudioRequestStatus eResult = eARS_SUCCESS;

            auto triggerInstances = pAudioObject->GetTriggerInstancesByOwner(pOwner);

            auto activeEvents = pAudioObject->GetActiveEvents();

            for (auto eventId : activeEvents)
            {
                const CATLEvent* const atlEvent = m_oAudioEventMgr.LookupID(eventId);

                if (atlEvent && triggerInstances.find(atlEvent->m_nTriggerInstanceID) != triggerInstances.end())
                {
                    EAudioRequestStatus eSingleResult = eARS_FAILURE;

                    switch (atlEvent->m_eSender)
                    {
                        case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                        {
                            AudioSystemImplementationRequestBus::BroadcastResult(eSingleResult, &AudioSystemImplementationRequestBus::Events::StopEvent,
                                pAudioObject->GetImplDataPtr(),
                                atlEvent->m_pImplData);
                            break;
                        }

                        case eAS_ATL_INTERNAL:
                        {
                            eSingleResult = StopInternalEvent(pAudioObject, atlEvent->m_pImplData);
                            break;
                        }

                        default:
                        {
                            g_audioLogger.Log(eALT_ERROR, "ATL - StopAllTriggersFiltered: Unknown ATL Recipient");
                            break;
                        }
                    }

                    if (eSingleResult != eARS_SUCCESS)
                    {
                        eResult = eARS_FAILURE;
                    }
                }
            }

            return eResult;
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::SetSwitchState(
        CATLAudioObjectBase* const pAudioObject,
        const CATLSwitchState* const pState)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        for (auto switchStateImpl : pState->m_cImplPtrs)
        {
            const EATLSubsystem eReceiver = switchStateImpl->GetReceiver();
            EAudioRequestStatus eSetStateResult = eARS_FAILURE;

            switch (eReceiver)
            {
                case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                {
                    AudioSystemImplementationRequestBus::BroadcastResult(eSetStateResult, &AudioSystemImplementationRequestBus::Events::SetSwitchState,
                        pAudioObject->GetImplDataPtr(),
                        switchStateImpl->m_pImplData);
                    break;
                }
                case eAS_ATL_INTERNAL:
                {
                    eSetStateResult = SetInternalSwitchState(pAudioObject, switchStateImpl->m_pImplData);
                    break;
                }
                default:
                {
                    g_audioLogger.Log(eALT_ERROR, "Unknown ATL Recipient");
                    break;
                }
            }

            if (eSetStateResult == eARS_SUCCESS)
            {
                eResult = eARS_SUCCESS;// if at least one of the implementations is set successfully, it is a success
            }
        }

        if (eResult == eARS_SUCCESS)
        {
            pAudioObject->SetSwitchState(pState->GetParentID(), pState->GetID());
        }
#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        else
        {
            const char* const sSwitchName = m_oDebugNameStore.LookupAudioSwitchName(pState->GetParentID());
            const char* const sSwitchStateName = m_oDebugNameStore.LookupAudioSwitchStateName(pState->GetParentID(), pState->GetID());
            const char* const sAudioObjectName = m_oDebugNameStore.LookupAudioObjectName(pAudioObject->GetID());
            g_audioLogger.Log(eALT_WARNING, "Failed to set the ATLSwitch \"%s\" to ATLSwitchState \"%s\" on AudioObject \"%s\" (ID: %u)", sSwitchName, sSwitchStateName, sAudioObjectName, pAudioObject->GetID());
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::SetRtpc(
        CATLAudioObjectBase* const pAudioObject,
        const CATLRtpc* const pRtpc,
        const float fValue)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        for (auto rtpcImpl : pRtpc->m_cImplPtrs)
        {
            const EATLSubsystem eReceiver = rtpcImpl->GetReceiver();
            EAudioRequestStatus eSetRtpcResult = eARS_FAILURE;

            switch (eReceiver)
            {
                case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                {
                    AudioSystemImplementationRequestBus::BroadcastResult(eSetRtpcResult, &AudioSystemImplementationRequestBus::Events::SetRtpc,
                        pAudioObject->GetImplDataPtr(),
                        rtpcImpl->m_pImplData,
                        fValue);
                    break;
                }
                case eAS_ATL_INTERNAL:
                {
                    eSetRtpcResult = SetInternalRtpc(pAudioObject, rtpcImpl->m_pImplData, fValue);
                    break;
                }
                default:
                {
                    g_audioLogger.Log(eALT_ERROR, "Unknown ATL Recipient");
                    break;
                }
            }

            if (eSetRtpcResult == eARS_SUCCESS)
            {
                eResult = eARS_SUCCESS;// if at least one of the implementations is set successfully, it is a success
            }
        }

        if (eResult == eARS_SUCCESS)
        {
            pAudioObject->SetRtpc(pRtpc->GetID(), fValue);
        }
#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        else
        {
            const char* const sRtpcName = m_oDebugNameStore.LookupAudioRtpcName(pRtpc->GetID());
            const char* const sAudioObjectName = m_oDebugNameStore.LookupAudioObjectName(pAudioObject->GetID());
            g_audioLogger.Log(eALT_WARNING, "Failed to set the ATLRtpc \"%s\" to %f on AudioObject \"%s\" (ID: %u)", sRtpcName, fValue, sAudioObjectName, pAudioObject->GetID());
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ResetRtpcs(CATLAudioObjectBase* const pAudioObject)
    {
        const TObjectRtpcMap rRtpcs = pAudioObject->GetRtpcs();
        EAudioRequestStatus eResult = eARS_SUCCESS;

        for (auto& rtpcPair : rRtpcs)
        {
            auto it = m_cRtpcs.find(rtpcPair.first);
            if (it != m_cRtpcs.end())
            {
                for (auto rtpcImpl : it->second->m_cImplPtrs)
                {
                    EAudioRequestStatus eResetRtpcResult = eARS_FAILURE;
                    switch (rtpcImpl->GetReceiver())
                    {
                        case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                        {
                            AudioSystemImplementationRequestBus::BroadcastResult(eResetRtpcResult, &AudioSystemImplementationRequestBus::Events::ResetRtpc,
                                pAudioObject->GetImplDataPtr(),
                                rtpcImpl->m_pImplData);
                            break;
                        }

                        case eAS_ATL_INTERNAL:
                        {
                            // Implement internal Rtpcs later
                            eResetRtpcResult = eARS_SUCCESS;
                            break;
                        }

                        default:
                        {
                            g_audioLogger.Log(eALT_ERROR, "ATL - ResetRtpc: Unknown ATL Recipient");
                            break;
                        }
                    }

                    // If any reset failed, consider it an overall failure
                    if (eResetRtpcResult != eARS_SUCCESS)
                    {
                        eResult = eARS_FAILURE;
                    }
                }
            }
        }

        if (eResult == eARS_SUCCESS)
        {
            pAudioObject->ClearRtpcs();
        }
#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        else
        {
            const TAudioObjectID objectId = pAudioObject->GetID();

            g_audioLogger.Log(eALT_WARNING,
                "Failed to Reset Rtpcs on AudioObject \"%s\" (ID: %u)",
                m_oDebugNameStore.LookupAudioObjectName(objectId),
                objectId);
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::SetEnvironment(
        CATLAudioObjectBase* const pAudioObject,
        const CATLAudioEnvironment* const pEnvironment,
        const float fAmount)
    {
        EAudioRequestStatus eResult = eARS_FAILURE;

        for (auto environmentImpl : pEnvironment->m_cImplPtrs)
        {
            const EATLSubsystem eReceiver = environmentImpl->GetReceiver();
            EAudioRequestStatus eSetEnvResult = eARS_FAILURE;

            switch (eReceiver)
            {
                case eAS_AUDIO_SYSTEM_IMPLEMENTATION:
                {
                    AudioSystemImplementationRequestBus::BroadcastResult(eSetEnvResult, &AudioSystemImplementationRequestBus::Events::SetEnvironment,
                        pAudioObject->GetImplDataPtr(),
                        environmentImpl->m_pImplData,
                        fAmount);
                    break;
                }
                case eAS_ATL_INTERNAL:
                {
                    eSetEnvResult = SetInternalEnvironment(pAudioObject, environmentImpl->m_pImplData, fAmount);
                    break;
                }
                default:
                {
                    g_audioLogger.Log(eALT_ERROR, "Unknown ATL Recipient");
                    break;
                }
            }

            if (eSetEnvResult == eARS_SUCCESS)
            {
                eResult = eARS_SUCCESS;// if at least one of the implementations is set successfully, it is a success
            }
        }

        if (eResult == eARS_SUCCESS)
        {
            pAudioObject->SetEnvironmentAmount(pEnvironment->GetID(), fAmount);
        }
#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        else
        {
            const char* const sEnvironmentName = m_oDebugNameStore.LookupAudioEnvironmentName(pEnvironment->GetID());
            const char* const sAudioObjectName = m_oDebugNameStore.LookupAudioObjectName(pAudioObject->GetID());
            g_audioLogger.Log(eALT_WARNING, "Failed to set the ATLAudioEnvironment \"%s\" to %f on AudioObject \"%s\" (ID: %u)", sEnvironmentName, fAmount, sAudioObjectName, pAudioObject->GetID());
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ResetEnvironments(CATLAudioObjectBase* const pAudioObject)
    {
        const TObjectEnvironmentMap rEnvironments = pAudioObject->GetEnvironments();

        EAudioRequestStatus eResult = eARS_SUCCESS;

        for (auto& environmentAmountPair : rEnvironments)
        {
            auto it = m_cEnvironments.find(environmentAmountPair.first);
            if (it != m_cEnvironments.end())
            {
                const EAudioRequestStatus eSetEnvResult = SetEnvironment(pAudioObject, it->second, 0.0f);

                if (eSetEnvResult != eARS_SUCCESS)
                {
                    // If setting at least one Environment fails, we consider this a failure.
                    eResult = eARS_FAILURE;
                }
            }
        }

        if (eResult == eARS_SUCCESS)
        {
            pAudioObject->ClearEnvironments();
        }
#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)
        else
        {
            const TAudioObjectID nObjectID = pAudioObject->GetID();

            g_audioLogger.Log(
                eALT_WARNING,
                "Failed to Reset AudioEnvironments on AudioObject \"%s\" (ID: %u)",
                m_oDebugNameStore.LookupAudioObjectName(nObjectID),
                nObjectID);
        }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE

        return eResult;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::ActivateInternalTrigger(
        CATLAudioObjectBase* const pAudioObject,
        const IATLTriggerImplData* const pTriggerData,
        IATLEventData* const pEventData)
    {
        //TODO implement
        return eARS_FAILURE;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::StopInternalEvent(
        CATLAudioObjectBase* const pAudioObject,
        const IATLEventData* const pEventData)
    {
        //TODO implement
        return eARS_FAILURE;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::StopAllInternalEvents(CATLAudioObjectBase* const pAudioObject)
    {
        //TODO implement
        return eARS_FAILURE;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::SetInternalRtpc(
        CATLAudioObjectBase* const pAudioObject,
        const IATLRtpcImplData* const pRtpcData,
        const float fValue)
    {
        //TODO implement
        return eARS_FAILURE;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::SetInternalSwitchState(
        CATLAudioObjectBase* const pAudioObject,
        const IATLSwitchStateImplData* const pSwitchStateData)
    {
        auto const pInternalStateData = static_cast<const SATLSwitchStateImplData_internal*>(pSwitchStateData);

        //TODO: once there is more than one internal switch, a more sensible approach needs to be developed
        if (pInternalStateData->nATLInternalSwitchID == ATLInternalControlIDs::ObstructionOcclusionCalcSwitchID)
        {
            if (pAudioObject->HasPosition())
            {
                auto const pPositionedAudioObject = static_cast<CATLAudioObject*>(pAudioObject);

                if (pInternalStateData->nATLInternalStateID == ATLInternalControlIDs::OOCStateIDs[eAOOCT_IGNORE])
                {
                    SATLSoundPropagationData oPropagationData;
#if AUDIO_ENABLE_CRY_PHYSICS
                    pPositionedAudioObject->SetObstructionOcclusionCalc(eAOOCT_IGNORE);
                    pPositionedAudioObject->GetPropagationData(oPropagationData);
#else
                    pPositionedAudioObject->SetRaycastCalcType(eAOOCT_IGNORE);
                    pPositionedAudioObject->GetObstOccData(oPropagationData);
#endif // AUDIO_ENABLE_CRY_PHYSICS

                    AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::SetObstructionOcclusion,
                        pPositionedAudioObject->GetImplDataPtr(),
                        oPropagationData.fObstruction,
                        oPropagationData.fOcclusion);
                }
                else if (pInternalStateData->nATLInternalStateID == ATLInternalControlIDs::OOCStateIDs[eAOOCT_SINGLE_RAY])
                {
#if AUDIO_ENABLE_CRY_PHYSICS
                    pPositionedAudioObject->SetObstructionOcclusionCalc(eAOOCT_SINGLE_RAY);
#else
                    pPositionedAudioObject->SetRaycastCalcType(eAOOCT_SINGLE_RAY);
#endif // AUDIO_ENABLE_CRY_PHYSICS
                }
                else if (pInternalStateData->nATLInternalStateID == ATLInternalControlIDs::OOCStateIDs[eAOOCT_MULTI_RAY])
                {
#if AUDIO_ENABLE_CRY_PHYSICS
                    pPositionedAudioObject->SetObstructionOcclusionCalc(eAOOCT_MULTI_RAY);
#else
                    pPositionedAudioObject->SetRaycastCalcType(eAOOCT_MULTI_RAY);
#endif // AUDIO_ENABLE_CRY_PHYSICS
                }
                else
                {
                    g_audioLogger.Log(eALT_WARNING, "SetInternalSwitchState - Unknown value specified for SetObstructionOcclusionCalc");
                }
            }
        }
        else if (pInternalStateData->nATLInternalSwitchID == ATLInternalControlIDs::ObjectVelocityTrackingSwitchID)
        {
            if (pAudioObject->HasPosition())
            {
                auto const pPositionedAudioObject = static_cast<CATLAudioObject*>(pAudioObject);
                if (pInternalStateData->nATLInternalStateID == ATLInternalControlIDs::OVTOnStateID)
                {
                    pPositionedAudioObject->SetVelocityTracking(true);
                }
                else if (pInternalStateData->nATLInternalStateID == ATLInternalControlIDs::OVTOffStateID)
                {
                    pPositionedAudioObject->SetVelocityTracking(false);
                }
                else
                {
                    g_audioLogger.Log(eALT_WARNING, "SetInternalSwitchState - Unknown value specified for SetVelocityTracking (ly-fixit update this name!)");
                }
            }
        }

        return eARS_SUCCESS;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::SetInternalEnvironment(
        CATLAudioObjectBase* const pAudioObject,
        const IATLEnvironmentImplData* const pEnvironmentImplData,
        const float fAmount)
    {
        // TODO: implement
        return eARS_FAILURE;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::MuteAll()
    {
        EAudioRequestStatus result = eARS_FAILURE;
        const CATLTrigger* const trigger = stl::find_in_map(m_cTriggers, ATLInternalControlIDs::MuteAllTriggerID, nullptr);

        if (trigger)
        {
            result = ActivateTrigger(m_pGlobalAudioObject, trigger, 0.0f);
        }
        else
        {
            g_audioLogger.Log(eALT_WARNING, "ATL - Trigger not found for: ATLInternalControlIDs::MuteAllTriggerID");
        }

        if (result == eARS_SUCCESS)
        {
            m_nFlags |= eAIS_IS_MUTED;
        }

        AudioSystemImplementationNotificationBus::Broadcast(&AudioSystemImplementationNotificationBus::Events::OnAudioSystemMuteAll);
        return result;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::UnmuteAll()
    {
        EAudioRequestStatus result = eARS_FAILURE;
        const CATLTrigger* const trigger = stl::find_in_map(m_cTriggers, ATLInternalControlIDs::UnmuteAllTriggerID, nullptr);

        if (trigger)
        {
            result = ActivateTrigger(m_pGlobalAudioObject, trigger, 0.0f);
        }
        else
        {
            g_audioLogger.Log(eALT_WARNING, "ATL - Trigger not found for: ATLInternalControlIDs::UnmuteAllTriggerID");
        }

        if (result == eARS_SUCCESS)
        {
            m_nFlags &= ~eAIS_IS_MUTED;
        }

        AudioSystemImplementationNotificationBus::Broadcast(&AudioSystemImplementationNotificationBus::Events::OnAudioSystemUnmuteAll);
        return result;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::UpdateSharedData()
    {
        m_oAudioListenerMgr.GetDefaultListenerPosition(m_oSharedData.m_oActiveListenerPosition);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::SetImplLanguage()
    {
        if (ICVar* pCVar = gEnv->pConsole->GetCVar("g_languageAudio"))
        {
            AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::SetLanguage, pCVar->GetString());
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam)
    {
#if AUDIO_ENABLE_CRY_PHYSICS
        switch (event)
        {
            case ESYSTEM_EVENT_LEVEL_UNLOAD:
            {
                CPropagationProcessor::s_canIssueRWIs = false;
                break;
            }
            case ESYSTEM_EVENT_LEVEL_GAMEPLAY_START:
            case ESYSTEM_EVENT_LEVEL_PRECACHE_START:
            {
                m_oAudioObjectMgr.ReleasePendingRays();
                CPropagationProcessor::s_canIssueRWIs = true;
                break;
            }
            case ESYSTEM_EVENT_LEVEL_POST_UNLOAD:
            {
                m_oAudioObjectMgr.ReleasePendingRays();
                break;
            }
            // Disable Obstruction raycasts in AI/Physics mode (LY-66446)
            // Bugs were discovered related to async obstruction raycasting,
            // deeper discovery and fixes are needed, this is a temporary patch.
            //case ESYSTEM_EVENT_EDITOR_SIMULATION_MODE_CHANGED:
            case ESYSTEM_EVENT_EDITOR_GAME_MODE_CHANGED:
            {
                if (wparam)
                {
                    // Game Mode begin or AI/Physics enabled
                    m_oAudioObjectMgr.ReleasePendingRays();
                    CPropagationProcessor::s_canIssueRWIs = true;
                }
                else
                {
                    // Editor Mode begin or AI/Physics disabled
                    CPropagationProcessor::s_canIssueRWIs = false;
                }
                break;
            }
            default:
            {
                break;
            }
        }
#else
        switch (event)
        {
        case ESYSTEM_EVENT_LEVEL_UNLOAD:
            RaycastProcessor::s_raycastsEnabled = false;
            break;
        case ESYSTEM_EVENT_LEVEL_GAMEPLAY_START:
        case ESYSTEM_EVENT_LEVEL_PRECACHE_START:
            RaycastProcessor::s_raycastsEnabled = true;
            break;
        case ESYSTEM_EVENT_EDITOR_GAME_MODE_CHANGED:
            RaycastProcessor::s_raycastsEnabled = (wparam != 0);
            break;
        default:
            break;
        }
#endif // AUDIO_ENABLE_CRY_PHYSICS
    }

#if defined(INCLUDE_AUDIO_PRODUCTION_CODE)

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    EAudioRequestStatus CAudioTranslationLayer::RefreshAudioSystem(const char* const controlsPath, const char* const levelName, TAudioPreloadRequestID levelPreloadId)
    {
        g_audioLogger.Log(eALT_ALWAYS, "$8Beginning to refresh the AudioSystem!");

        if (!controlsPath || controlsPath[0] == '\0')
        {
            g_audioLogger.Log(eALT_ERROR, "ATL RefreshAudioSystem - Controls path is null, can't complete the refresh!");
            return eARS_FAILURE;
        }

        EAudioRequestStatus eResult = eARS_FAILURE;
        AudioSystemImplementationRequestBus::BroadcastResult(eResult, &AudioSystemImplementationRequestBus::Events::StopAllSounds);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to StopAllSounds!");

        eResult = m_oFileCacheMgr.UnloadDataByScope(eADS_LEVEL_SPECIFIC);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to unload old level data!");

        eResult = m_oFileCacheMgr.UnloadDataByScope(eADS_GLOBAL);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to unload old global data!");

        eResult = ClearControlsData(eADS_ALL);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to clear old controls data!");

        eResult = ClearPreloadsData(eADS_ALL);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to clear old preloads data!");

        AudioSystemImplementationNotificationBus::Broadcast(&AudioSystemImplementationNotificationBus::Events::OnAudioSystemRefresh);

        SetImplLanguage();

        eResult = ParseControlsData(controlsPath, eADS_GLOBAL);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to parse fresh global controls data!");

        eResult = ParsePreloadsData(controlsPath, eADS_GLOBAL);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to parse fresh global preloads data!");

        eResult = m_oFileCacheMgr.TryLoadRequest(ATLInternalControlIDs::GlobalPreloadRequestID, true, true);
        AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to load fresh global preloads!");

        if (levelName && levelName[0] != '\0')
        {
            AZStd::string levelControlsPath(controlsPath);
            levelControlsPath.append("levels/");
            levelControlsPath.append(levelName);
            AZ::StringFunc::RelativePath::Normalize(levelControlsPath);

            eResult = ParseControlsData(levelControlsPath.c_str(), eADS_LEVEL_SPECIFIC);
            AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to parse fresh level controls data!");

            eResult = ParsePreloadsData(levelControlsPath.c_str(), eADS_LEVEL_SPECIFIC);
            AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to parse fresh level preloads data!");

            if (levelPreloadId != INVALID_AUDIO_PRELOAD_REQUEST_ID)
            {
                eResult = m_oFileCacheMgr.TryLoadRequest(levelPreloadId, true, true);
                AZ_Error("AudioTranslationLayer", eResult == eARS_SUCCESS, "ATL RefreshAudioSystem - Failed to load fresh level preloads!");
            }
        }

        if (m_nFlags & eAIS_IS_MUTED)
        {
            // restore the muted state...
            MuteAll();
        }

        g_audioLogger.Log(eALT_ALWAYS, "$3Done refreshing the AudioSystem!");

        return eARS_SUCCESS;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    bool CAudioTranslationLayer::ReserveAudioObjectID(TAudioObjectID& rAudioObjectID, const char* const sAudioObjectName)
    {
        const bool bSuccess = m_oAudioObjectMgr.ReserveID(rAudioObjectID, sAudioObjectName);

        if (bSuccess)
        {
            m_oDebugNameStore.AddAudioObject(rAudioObjectID, sAudioObjectName);
        }

        return bSuccess;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::DrawAudioSystemDebugInfo()
    {
        AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Audio);

        if (IRenderAuxGeom* pAuxGeom = (g_audioCVars.m_nDrawAudioDebug > 0 && gEnv->pRenderer) ? gEnv->pRenderer->GetIRenderAuxGeom() : nullptr)
        {
            DrawAudioObjectDebugInfo(*pAuxGeom); // needs to be called first so that the rest of the labels are printed
            // on top (Draw2dLabel doesn't provide a way set which labels are printed on top)

            const size_t nPrimaryPoolSize = AZ::AllocatorInstance<Audio::AudioSystemAllocator>::Get().Capacity();
            const size_t nPrimaryPoolUsedSize = nPrimaryPoolSize - AZ::AllocatorInstance<Audio::AudioSystemAllocator>::Get().GetUnAllocatedMemory();

            float fPosX = 0.0f;
            float fPosY = 4.0f;

            const float fColor[4] = { 1.0f, 1.0f, 1.0f, 0.9f };
            const float fColorRed[4] = { 1.0f, 0.0f, 0.0f, 0.7f };
            const float fColorGreen[4] = { 0.0f, 1.0f, 0.0f, 0.7f };
            const float fColorBlue[4] = { 0.4f, 0.4f, 1.0f, 1.0f };

            const char* implementationName = nullptr;
            AudioSystemImplementationRequestBus::BroadcastResult(implementationName, &AudioSystemImplementationRequestBus::Events::GetImplementationNameString);
            pAuxGeom->Draw2dLabel(fPosX, fPosY, 1.6f, fColorBlue, false, "AudioTranslationLayer with %s", implementationName);

            fPosX += 20.0f;
            fPosY += 17.0f;

            pAuxGeom->Draw2dLabel(fPosX, fPosY, 1.35f, fColor, false,
                "AudioSystem Memory: %.2f / %.2f MiB",
                (nPrimaryPoolUsedSize / 1024) / 1024.f,
                (nPrimaryPoolSize / 1024) / 1024.f
                );


            float const fLineHeight = 13.0f;

            SAudioImplMemoryInfo oMemoryInfo;
            AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::GetMemoryInfo, oMemoryInfo);

            fPosY += fLineHeight;
            pAuxGeom->Draw2dLabel(fPosX, fPosY, 1.35f, fColor, false,
                "AudioImpl Memory: %.2f / %.2f MiB",
                (oMemoryInfo.nPrimaryPoolUsedSize / 1024) / 1024.f,
                (oMemoryInfo.nPrimaryPoolSize / 1024) / 1024.f
                );

            static const float SMOOTHING_ALPHA = 0.2f;

            const AZ::Vector3 vPos = m_oSharedData.m_oActiveListenerPosition.GetPositionVec();
            const AZ::Vector3 vFwd = m_oSharedData.m_oActiveListenerPosition.GetForwardVec();
            const size_t nNumAudioObjects = m_oAudioObjectMgr.GetNumAudioObjects();
            const size_t nNumActiveAudioObjects = m_oAudioObjectMgr.GetNumActiveAudioObjects();
            const size_t nEvents = m_oAudioEventMgr.GetNumActive();
            const size_t nListeners = m_oAudioListenerMgr.GetNumActive();
            const size_t nNumEventListeners = m_oAudioEventListenerMgr.GetNumEventListeners();

#if AUDIO_ENABLE_CRY_PHYSICS
            static float fSyncRays = 0;
            static float fAsyncRays = 0;
            fSyncRays += (CPropagationProcessor::s_nTotalSyncPhysRays - fSyncRays) * SMOOTHING_ALPHA;
            fAsyncRays += (CPropagationProcessor::s_nTotalAsyncPhysRays - fAsyncRays) * SMOOTHING_ALPHA * 0.1f;
#endif // AUDIO_ENABLE_CRY_PHYSICS

            const bool bActive = true;
            const float fColorListener[4] =
            {
                bActive ? fColorGreen[0] : fColorRed[0],
                bActive ? fColorGreen[1] : fColorRed[1],
                bActive ? fColorGreen[2] : fColorRed[2],
                1.0f
            };

            const float* fColorNumbers = fColorBlue;

            TAudioObjectID activeListenerID = INVALID_AUDIO_OBJECT_ID;
            if (CATLListenerObject* overrideListener = m_oAudioListenerMgr.LookupID(m_oAudioListenerMgr.GetOverrideListenerID()))
            {
                activeListenerID = overrideListener->GetID();
            }
            else if (CATLListenerObject* defaultListener = m_oAudioListenerMgr.LookupID(m_oAudioListenerMgr.GetDefaultListenerID()))
            {
                activeListenerID = defaultListener->GetID();
            }

            fPosY += fLineHeight;
            pAuxGeom->Draw2dLabel(fPosX, fPosY, 1.35f, fColorListener, false,
                "Listener <%llu> PosXYZ: %.2f %.2f %.2f FwdXYZ: %.2f %.2f %.2f",
                activeListenerID,
                static_cast<float>(vPos.GetX()), static_cast<float>(vPos.GetY()), static_cast<float>(vPos.GetZ()),
                static_cast<float>(vFwd.GetX()), static_cast<float>(vFwd.GetY()), static_cast<float>(vFwd.GetZ()));

            fPosY += fLineHeight;
            pAuxGeom->Draw2dLabel(fPosX, fPosY, 1.35f, fColorNumbers, false,
#if AUDIO_ENABLE_CRY_PHYSICS
                "Objects: %3zu/%3zu | Events: %3zu  EventListeners %3zu | Listeners: %zu | SyncRays: %3.1f  AsyncRays: %3.1f",
                nNumActiveAudioObjects, nNumAudioObjects, nEvents, nNumEventListeners, nListeners, fSyncRays, fAsyncRays);
#else
                "Objects: %3zu/%3zu | Events: %3zu  EventListeners %3zu | Listeners: %zu",
                nNumActiveAudioObjects, nNumAudioObjects, nEvents, nNumEventListeners, nListeners);
#endif // AUDIO_ENABLE_CRY_PHYSICS

            fPosY += fLineHeight;
            DrawATLComponentDebugInfo(*pAuxGeom, fPosX, fPosY);

            pAuxGeom->Commit(7);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::DrawATLComponentDebugInfo(IRenderAuxGeom& auxGeom, float fPosX, const float fPosY)
    {
        m_oFileCacheMgr.DrawDebugInfo(auxGeom, fPosX, fPosY);

        if ((g_audioCVars.m_nDrawAudioDebug & eADDF_SHOW_IMPL_MEMORY_POOL_USAGE) != 0)
        {
            DrawImplMemoryPoolDebugInfo(auxGeom, fPosX, fPosY);
        }

        if ((g_audioCVars.m_nDrawAudioDebug & eADDF_SHOW_ACTIVE_OBJECTS) != 0)
        {
            m_oAudioObjectMgr.DrawDebugInfo(auxGeom, fPosX, fPosY);
            fPosX += 800.0f;
        }

        if ((g_audioCVars.m_nDrawAudioDebug & eADDF_SHOW_ACTIVE_EVENTS) != 0)
        {
            m_oAudioEventMgr.DrawDebugInfo(auxGeom, fPosX, fPosY);
        }

        if ((g_audioCVars.m_nDrawAudioDebug & eADDF_DRAW_LISTENER_SPHERE) != 0)
        {
            m_oAudioListenerMgr.DrawDebugInfo(auxGeom);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void BytesToString(AZ::u32 bytes, char* buffer, size_t bufLength)
    {
        if (bytes < (1 << 10))
        {
            azsnprintf(buffer, bufLength, "%u B", bytes);
        }
        else if (bytes < (1 << 20))
        {
            azsnprintf(buffer, bufLength, "%.1f KB", static_cast<float>(bytes) / static_cast<float>(1 << 10));
        }
        else
        {
            azsnprintf(buffer, bufLength, "%.1f MB", static_cast<float>(bytes) / static_cast<float>(1 << 20));
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::DrawImplMemoryPoolDebugInfo(IRenderAuxGeom& auxGeom, float fPosX, float fPosY)
    {
        const float colorMax = 0.9f;
        const float colorMin = 0.1f;
        const float textSize = 1.5f;
        float color[4] = { colorMax, colorMax, colorMax, 0.9f };

        const AZ::Color greenColor(colorMin, colorMax, colorMin, 0.9f);
        const AZ::Color yellowColor(colorMax, colorMax, colorMin, 0.9f);
        const AZ::Color redColor(colorMax, colorMin, colorMin, 0.9f);

        const char* tableHeaderNames[8] = {
            "ID",
            "Name",
            "Total Size",
            "Used Size",
            "Used %%",
            "Peak Used",
            "Allocs",
            "Frees",
        };
        const float xTablePositions[8] = { 0.f, 40.f, 300.f, 400.f, 500.f, 600.f, 700.f, 800.f };

        float posY = fPosY;

        // Draw the header info:
        for (int column = 0; column < 8; ++column)
        {
            auxGeom.Draw2dLabel(fPosX + xTablePositions[column], posY, 1.5f, color, false, tableHeaderNames[column]);
        }

        // Get the memory pool information...
        AZStd::vector<AudioImplMemoryPoolInfo> poolInfos;
        AudioSystemImplementationRequestBus::BroadcastResult(poolInfos, &AudioSystemImplementationRequestBus::Events::GetMemoryPoolInfo);

        if (!poolInfos.empty())
        {
            const size_t bufferSize = 32;
            char buffer[bufferSize] = { 0 };

            for (auto& poolInfo : poolInfos)
            {
                posY += 15.f;

                auto percentUsed = static_cast<float>(poolInfo.m_memoryUsed) / static_cast<float>(poolInfo.m_memoryReserved);

                // Calculate a color (green->-yellow->-red) based on percentage.
                AZ::Color percentColor;
                if (percentUsed < 0.5f)
                {
                    percentColor = greenColor.Lerp(yellowColor, percentUsed * 2.f);
                }
                else
                {
                    percentColor = yellowColor.Lerp(redColor, (percentUsed * 2.f) - 1.f);
                }
                percentColor.StoreToFloat4(color);

                // ID
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[0], posY, textSize, color, false, "%d", poolInfo.m_poolId);

                // Name
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[1], posY, textSize, color, false, "%s", poolInfo.m_poolName);

                // Total Size (bytes)
                BytesToString(poolInfo.m_memoryReserved, buffer, bufferSize);
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[2], posY, textSize, color, false, "%s", buffer);

                // Used Size (bytes)
                BytesToString(poolInfo.m_memoryUsed, buffer, bufferSize);
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[3], posY, textSize, color, false, "%s", buffer);

                // Used %
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[4], posY, textSize, color, false, "%.1f %%", percentUsed * 100.f);

                // Peak Used (bytes)
                BytesToString(poolInfo.m_peakUsed, buffer, bufferSize);
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[5], posY, textSize, color, false, "%s", buffer);

                // Allocs
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[6], posY, textSize, color, false, "%u", poolInfo.m_numAllocs);

                // Frees
                auxGeom.Draw2dLabel(
                    fPosX + xTablePositions[7], posY, textSize, color, false, "%u", poolInfo.m_numFrees);
            }
        }
        else
        {
            auxGeom.Draw2dLabel(
                fPosX, posY + 15.f, 1.5f, color, false, "No memory pool information is available for display!");
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    void CAudioTranslationLayer::DrawAudioObjectDebugInfo(IRenderAuxGeom& auxGeom)
    {
        SATLWorldPosition oListenerPosition;
        m_oAudioListenerMgr.GetDefaultListenerPosition(oListenerPosition);
        m_oAudioObjectMgr.DrawPerObjectDebugInfo(auxGeom, oListenerPosition.GetPositionVec());
    }
#endif // INCLUDE_AUDIO_PRODUCTION_CODE
} // namespace Audio