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

#include "LmbrCentral_precompiled.h"
#include "Source/Rendering/EditorLensFlareComponent.h"

#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>

#include <Mocks/ISystemMock.h>
#include <Mocks/IConsoleMock.h>

using ::testing::NiceMock;

namespace UnitTest
{
    // Provides public access to protected/private members to allow for testing.
    class Test_EditorLensFlareComponent :
        public LmbrCentral::EditorLensFlareComponent
    {
    public:
        AZ_COMPONENT(Test_EditorLensFlareComponent, "{2FB6C076-BB92-47A3-93C1-6ED7E622D7E0}", LmbrCentral::EditorLensFlareComponent);

        Test_EditorLensFlareComponent() : EditorLensFlareComponent()
        {

        }

        ~Test_EditorLensFlareComponent() override
        {

        }

        static void Reflect(AZ::ReflectContext* context)
        {
            AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);

            if (serializeContext)
            {
                serializeContext->Class<Test_EditorLensFlareComponent, EditorLensFlareComponent>()->
                    Version(1);
            }
        }

        LmbrCentral::EditorLensFlareConfiguration Public_GetEditorLensFlareConfiguration() const
        {
            return GetEditorLensFlareConfiguration();
        }
    };

    class MockLensFlareHandlerAndCatalog
        : public AZ::Data::AssetHandler
        , public AZ::Data::AssetCatalog
    {
    public:
        AZ_CLASS_ALLOCATOR(MockLensFlareHandlerAndCatalog, AZ::SystemAllocator, 0);

        AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& /*type*/) override
        {
            return aznew LmbrCentral::LensFlareAsset();
        }

        bool LoadAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& /*asset*/, AZ::IO::GenericStream* /*stream*/, const AZ::Data::AssetFilterCB& /*assetLoadFilterCB*/) override
        {
            return true;
        }

        bool LoadAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& /*asset*/, const char* /*assetPath*/, const AZ::Data::AssetFilterCB& /*assetLoadFilterCB*/) override
        {
            return true;
        }

        void DestroyAsset(AZ::Data::AssetPtr ptr) override
        {
            delete ptr;
        }

        void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override
        {
            assetTypes.push_back(AZ::AzTypeInfo<LmbrCentral::LensFlareAsset>::Uuid());
        }

        AZ::Data::AssetStreamInfo GetStreamInfoForLoad(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& /*type*/) override
        {
            AZ::Data::AssetStreamInfo info;
            // Set valid stream info. This ensures the asset load doesn't result in an
            // error on another thread that can occur during shutdown.
            info.m_streamName = AZStd::string::format("MockLensFlareHandlerAndCatalog%s", assetId.ToString<AZStd::string>().c_str());
            info.m_dataOffset = 0;
            info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
            info.m_isCustomStreamType = true;
            return info;
        }
    };

    class EditorLensFlareComponentTests
        : public ::testing::Test
        , public UnitTest::TraceBusRedirector
        , public AZ::Data::AssetBus::MultiHandler
    {
    protected:
        //////////////////////////////////////////////////////////////////////////
        // AZ::Data::AssetBus::MultiHandler

        void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override
        {
            EXPECT_EQ(asset.GetId(), m_waitForAssetIdLoad);
            m_waitForAssetIdLoad.SetInvalid();
            m_assetLoadSemaphore.release();
        }

        void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override
        {
            // No asset errors should happen during this test.
            EXPECT_TRUE(false);
        }

        //////////////////////////////////////////////////////////////////////////
        void SetUp() override
        {
            m_app.Start(m_descriptor);

            AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);

            m_EditorLensFlareComponentDescriptor = LmbrCentral::EditorLensFlareComponent::CreateDescriptor();
            m_testEditorLensFlareComponentDescriptor = Test_EditorLensFlareComponent::CreateDescriptor();
            m_lensFlareComponentDescriptor = LmbrCentral::LensFlareComponent::CreateDescriptor();

            m_testEditorLensFlareComponentDescriptor->Reflect(m_app.GetSerializeContext());
            m_lensFlareComponentDescriptor->Reflect(m_app.GetSerializeContext());
            AZ::Debug::TraceMessageBus::Handler::BusConnect();

            AZ::Data::AssetManager::Instance().RegisterHandler(&m_lensFlareHandlerAndCatalog, AZ::AzTypeInfo<LmbrCentral::LensFlareAsset>::Uuid());
            AZ::Data::AssetManager::Instance().RegisterCatalog(&m_lensFlareHandlerAndCatalog, AZ::AzTypeInfo<LmbrCentral::LensFlareAsset>::Uuid());

            EXPECT_CALL(m_system, GetConfigSpec(::testing::_))
                .WillRepeatedly(::testing::Return(CONFIG_AUTO_SPEC));

            // Setup Mocks on a stub environment
            m_stubEnv.pConsole = &m_console;
            m_stubEnv.pSystem = &m_system;
            m_priorEnv = gEnv;
            gEnv = &m_stubEnv;
        }
        void TearDown() override
        {
            gEnv = m_priorEnv;

            if (m_waitForAssetIdLoad.IsValid())
            {
                const int assetLoadSleepMS = 20;
                int totalWaitTimeMS = 5000;
                bool assetLoaded = false;
                while (!assetLoaded && totalWaitTimeMS > 0)
                {
                    assetLoaded = m_assetLoadSemaphore.try_acquire_for(AZStd::chrono::milliseconds(assetLoadSleepMS));
                    AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
                    if (!assetLoaded)
                    {
                        totalWaitTimeMS -= assetLoadSleepMS;
                    }
                }
                EXPECT_GT(totalWaitTimeMS, 0);
                EXPECT_TRUE(assetLoaded);
                AZ::Data::AssetBus::MultiHandler::BusDisconnect();
            }

            AZ::Data::AssetManager::Instance().UnregisterHandler(&m_lensFlareHandlerAndCatalog);
            AZ::Data::AssetManager::Instance().UnregisterCatalog(&m_lensFlareHandlerAndCatalog);

            m_EditorLensFlareComponentDescriptor->ReleaseDescriptor();
            m_testEditorLensFlareComponentDescriptor->ReleaseDescriptor();
            m_lensFlareComponentDescriptor->ReleaseDescriptor();
            m_app.Stop();

            // Disconnect last, ToolsApplication::Stop() can result in messages that we are using 
            // TraceMessageBus to handle.
            AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
        }

        void SetupLensFlareEntity(AZ::Entity& entity)
        {
            entity.CreateComponent<AzFramework::TransformComponent>();
            entity.CreateComponent<Test_EditorLensFlareComponent>();

            entity.Init();
            entity.Activate();
        }

        AzToolsFramework::ToolsApplication m_app;
        AZ::ComponentApplication::Descriptor m_descriptor;

        MockLensFlareHandlerAndCatalog m_lensFlareHandlerAndCatalog;

        AZ::ComponentDescriptor* m_EditorLensFlareComponentDescriptor = nullptr;
        AZ::ComponentDescriptor* m_testEditorLensFlareComponentDescriptor = nullptr;
        AZ::ComponentDescriptor* m_lensFlareComponentDescriptor = nullptr;

        // If this is set to a valid asset ID, teardown won't finish until
        // this asset has been loaded. If this takes too long, teardown will error out.
        AZ::Data::AssetId m_waitForAssetIdLoad;
        AZStd::binary_semaphore m_assetLoadSemaphore;

        SSystemGlobalEnvironment m_stubEnv;
        NiceMock<SystemMock> m_system;
        NiceMock<ConsoleMock> m_console;
        SSystemGlobalEnvironment* m_priorEnv = nullptr;
    };

    TEST_F(EditorLensFlareComponentTests, EditorLensFlareComponent_AddEditorLensFlareComponent_ComponentExists)
    {
        AZ::Entity entity;
        SetupLensFlareEntity(entity);

        Test_EditorLensFlareComponent* lensFlareComponent =
            entity.FindComponent<Test_EditorLensFlareComponent>();
        EXPECT_TRUE(lensFlareComponent != nullptr);
    }

    TEST_F(EditorLensFlareComponentTests, EditorLensFlareComponent_SetAssetId_GetAssetIdMatchesSet)
    {
        AZ::Entity entity;
        SetupLensFlareEntity(entity);

        // Use an arbitrary, non-default asset ID to verify the set & get work.
        AZ::Data::AssetId assetIdToSet(AZ::Uuid("{377939BD-57BB-4476-B7B5-35A162B1335E}"), 5);
        // m_waitForAssetIdLoad is cleared when the asset load finishes, so keep a local copy of the asset ID.
        m_waitForAssetIdLoad = assetIdToSet;
        AZ::Data::AssetBus::MultiHandler::BusConnect(m_waitForAssetIdLoad);

        Test_EditorLensFlareComponent* lensFlareComponent =
            entity.FindComponent<Test_EditorLensFlareComponent>();
        lensFlareComponent->SetPrimaryAsset(assetIdToSet);

        LmbrCentral::EditorLensFlareConfiguration lensFlareConfiguration =
            lensFlareComponent->Public_GetEditorLensFlareConfiguration();
        EXPECT_EQ(lensFlareConfiguration.m_asset.GetId(), assetIdToSet);
    }

    TEST_F(EditorLensFlareComponentTests, EditorLensFlareComponent_SetAssetIdBuildGameEntity_GameEntityHasAssetId)
    {
        AZ::Entity entity;
        SetupLensFlareEntity(entity);

        // Use an arbitrary, non-default asset ID to verify the set & get work.
        AZ::Data::AssetId assetIdToSet(AZ::Uuid("{BC7C7A76-C5F2-44E2-8E32-3CE03C3C5ADE}"), 6);
        // m_waitForAssetIdLoad is cleared when the asset load finishes, so keep a local copy of the asset ID.
        m_waitForAssetIdLoad = assetIdToSet;
        AZ::Data::AssetBus::MultiHandler::BusConnect(m_waitForAssetIdLoad);

        Test_EditorLensFlareComponent* lensFlareComponent =
            entity.FindComponent<Test_EditorLensFlareComponent>();
        lensFlareComponent->SetPrimaryAsset(assetIdToSet);

        AZ::Entity gameEntity;
        lensFlareComponent->BuildGameEntity(&gameEntity);
        LmbrCentral::LensFlareComponent* gameComponent =
            gameEntity.FindComponent<LmbrCentral::LensFlareComponent>();
        EXPECT_TRUE(gameComponent != nullptr);
        EXPECT_EQ(gameComponent->GetLensFlareConfiguration().m_asset.GetId(), assetIdToSet);

    }
} // namespace UnitTest