// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#include "AwsGameKitEditor.h"

// GameKit
#include "Achievements/EditorAchievementFeatureExample.h"
#include "AwsCredentialsManager.h"
#include "AwsGameKitCore.h"
#include "AwsGameKitFeatureControlCenter.h"
#include "AwsGameKitRuntime.h"
#include "AwsGameKitSettings.h"
#include "AwsGameKitSettingsLayoutDetails.h"
#include "AwsGameKitStyleSet.h"
#include "EditorState.h"
#include "FeatureResourceManager.h"
#include "GameSaving/EditorGameSavingFeatureExample.h"
#include "Identity/EditorIdentityFeatureExample.h"
#include "IGameKitEditorFeatureExample.h"
#include "UserGameplayData/EditorUserGameplayFeatureExample.h"

// Standard Library
#include <stdexcept>

// Unreal
#include "Async/Async.h"
#include "ISettingsModule.h"
#include "ISettingsSection.h"
#include "MessageEndpointBuilder.h"
#include "MessagingCommon/Public/MessageEndpoint.h"

#define LOCTEXT_NAMESPACE "FAwsGameKitEditor"

#if UE_BUILD_DEVELOPMENT && WITH_EDITOR
DEFINE_LOG_CATEGORY(LogAwsGameKit);
#endif

void FAwsGameKitEditorModule::AddGameKitFeatureExample(FPropertyEditorModule& propertyModule, IGameKitEditorFeatureExample* gamekitFeatureExample)
{
    // Add feature to map
    this->gamekitFeatureExamples.Add(gamekitFeatureExample->GetFeatureExampleClassName(), gamekitFeatureExample);

    // Register custom detail panel for example
    propertyModule.RegisterCustomClassLayout(gamekitFeatureExample->GetFeatureExampleClassName(), gamekitFeatureExample->GetDetailCustomizationForExampleCreationMethod());
}

void FAwsGameKitEditorModule::OpenProjectSettings()
{
    ISettingsModule* settingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
    if (settingsModule != nullptr)
    {
        settingsModule->ShowViewer("Project", "Plugins", "AWS GameKit");
    }
}

TSharedPtr<EditorState> FAwsGameKitEditorModule::GetEditorState() const
{
    return this->editorState;
}

TSharedPtr<AwsCredentialsManager> FAwsGameKitEditorModule::GetCredentialsManager() const
{
    return this->credentialsManager;
}

TSharedPtr<FeatureResourceManager> FAwsGameKitEditorModule::GetFeatureResourceManager() const
{
    return this->featureResourceManager;
}

TSharedPtr<AwsGameKitFeatureControlCenter> FAwsGameKitEditorModule::GetFeatureControlCenter() const
{
    return this->featureControlCenter;
}

TSharedPtr<FMessageEndpoint, ESPMode::ThreadSafe> FAwsGameKitEditorModule::GetMessageBus() const
{
    return this->messageEndpoint;
}

void FAwsGameKitEditorModule::StartupModule()
{
    UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::StartupModule()"));
    FPropertyEditorModule& propertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");

    // Initialize Editor State
    this->editorState = MakeShareable(new EditorState());

    // Initialize Message Bus
    this->messageEndpoint = FMessageEndpoint::Builder(AWSGAMEKIT_EDITOR_MESSAGE_BUS_NAME)
        .Handling<FMsgCredentialsState>(this->editorState.Get(), &EditorState::CredentialsStateMessageHandler)
        .Build();
    this->messageEndpoint->Subscribe<FMsgCredentialsState>();

    // Initialize CredentialsManager
    this->credentialsManager = MakeShareable(new AwsCredentialsManager());

    // Initialize StyleSet
    AwsGameKitStyleSet();

    // Initialize FeatureResourceManager
    this->featureResourceManager = MakeShareable(new FeatureResourceManager());
    this->featureResourceManager->Initialize();

    // Initialize Feature Control Center
    this->featureControlCenter = MakeShareable(new AwsGameKitFeatureControlCenter(AWSGAMEKIT_EDITOR_MODULE_INSTANCE()));

    // Add GameKit feature examples to editor
    AddGameKitFeatureExample(propertyModule, (IGameKitEditorFeatureExample*) new EditorIdentityFeatureExample());
    AddGameKitFeatureExample(propertyModule, (IGameKitEditorFeatureExample*) new EditorAchievementFeatureExample());
    AddGameKitFeatureExample(propertyModule, (IGameKitEditorFeatureExample*) new EditorUserGameplayFeatureExample());
    AddGameKitFeatureExample(propertyModule, (IGameKitEditorFeatureExample*) new EditorGameSavingFeatureExample());

    // Restore existing state if it exists
    this->BootstrapExistingState();

    // Register project settings after all AWS GameKit modules have come online
    FCoreDelegates::OnPostEngineInit.AddRaw(this, &FAwsGameKitEditorModule::RegisterProjectSettings);
}

void FAwsGameKitEditorModule::BootstrapExistingState()
{
    UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::BootstrapExistingState()"));
    const FString& projectRoot = this->featureResourceManager->GetRootPath();
    TArray<FString> saveInfoFiles;
    IFileManager::Get().FindFilesRecursive(saveInfoFiles, *projectRoot, TEXT("saveInfo.yml"), true, false, true);

    if (saveInfoFiles.Num() > 0)
    {
        AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, saveInfoFiles]()
        {
            // Wrap this in a try because there are many things that could go wrong:
            // * saveInfo.yml corrupted (manual editing?)
            // * GetAccountId() failed with no network connection
            // * accessKey/secretKey removed from ~/.aws/credentials
            try
            {
                // Extract the game name from the saveInfo.yml file path; it should be the directory name directly above it.
                FString gameName = FPaths::GetPathLeaf(FPaths::GetPath(saveInfoFiles[0]));

                // Set the game name, sparking the feature resource manager to reload settings.
                // Dev environment and auxilliary settings will be loaded; do not read feature specific settings until the correct environment is specified later.
                this->featureResourceManager->SetGameName(gameName);

                AccountDetails accountDetails;
                accountDetails.gameName = gameName;
                accountDetails.environment = this->featureResourceManager->GetLastUsedEnvironment();
                accountDetails.region = this->featureResourceManager->GetLastUsedRegion();

                // Initialize our credentials manager
                this->credentialsManager->SetGameName(accountDetails.gameName);
                this->credentialsManager->SetEnv(accountDetails.environment);

                // Credentials are loaded from {userDocumentsDir}/../.aws/credentials
                accountDetails.accessKey = this->credentialsManager->GetAccessKey();
                accountDetails.accessSecret = this->credentialsManager->GetSecretKey();
                accountDetails.accountId = this->featureResourceManager->GetAccountId(accountDetails.accessKey, accountDetails.accessSecret);

                // Ensure we have the information necessary before initializing FeatureResourceManager
                if (accountDetails.accessKey.IsEmpty() || accountDetails.accessSecret.IsEmpty() || accountDetails.accountId.IsEmpty() || accountDetails.region.IsEmpty())
                {
                    throw std::runtime_error("Existing state lacking complete AWS credentials.");
                }

                // Triggers a settings reload, ensuring all features are initialized with the proper settings for the last used environment
                this->featureResourceManager->SetAccountDetails(accountDetails);

                // Store credentials and mark as submitted
                this->editorState->SetCredentials(accountDetails);
                this->editorState->SetCredentialState(true);

                // Ensure account has been bootstrapped
                this->featureResourceManager->BootstrapAccount();

                // Tell runtime module to reload the config file for the last used environment
                FAwsGameKitRuntimeModule* runtimeModule = FModuleManager::GetModulePtr<FAwsGameKitRuntimeModule>("AwsGameKitRuntime");
                runtimeModule->ReloadConfigFile(this->featureResourceManager->GetClientConfigSubdirectory());

                // "Prime the pump" with our feature statuses
                this->featureControlCenter->RefreshFeatureStatuses();
            }
            catch (const std::exception& e)
            {
                UE_LOG(LogAwsGameKit, Error, TEXT("FAwsGameKitEditorModule::BootstrapExistingState() failed: %s"), *FString(ANSI_TO_TCHAR(e.what())));
            }
        });
    }
}

void FAwsGameKitEditorModule::ShutdownModule()
{
    UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::ShutdownModule()"));
    FPropertyEditorModule& propertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");

    // Unregister all custom examples Details Panels:
    for (auto const& keyValue : this->gamekitFeatureExamples)
    {
        UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::ShutdownModule(): Unregistering CustomPropertyTypeLayout: %s"), *keyValue.Key.ToString());
        propertyModule.UnregisterCustomPropertyTypeLayout(keyValue.Key);
    }

    UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::ShutdownModule(): All CustomPropertyTypeLayout unregistered"));
    this->credentialsManager.Reset();
    this->editorState.Reset();
    this->featureResourceManager.Reset();
    this->gamekitFeatureExamples.Reset();
    this->messageEndpoint.Reset();
    this->featureControlCenter.Reset();

    UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::ShutdownModule(): Unregistering GameKit Project Settings"));
    UnregisterProjectSettings();
}

void FAwsGameKitEditorModule::RegisterProjectSettings() const
{
    ISettingsModule* settingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
    if (settingsModule != nullptr)
    {
        // Register the AWS GameKit section
        settingsModule->RegisterSettings("Project", "Plugins", "AWS GameKit",
            FText::FromString("AWS GameKit"),
            FText::FromString("Configuration for AWS GameKit features"),
            GetMutableDefault<UAwsGameKitSettings>());

        // Combine all AWS GameKit configurations into a single detail panel for the settings section
        FPropertyEditorModule& propertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
        propertyModule.RegisterCustomClassLayout(UAwsGameKitSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&AwsGameKitSettingsLayoutDetails::MakeInstance));
    }
}

void FAwsGameKitEditorModule::UnregisterProjectSettings() const
{
    ISettingsModule* settingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
    if (settingsModule != nullptr)
    {
        settingsModule->UnregisterSettings("Project", "Plugins", "AWS GameKit");
        UE_LOG(LogAwsGameKit, Display, TEXT("FAwsGameKitEditorModule::ShutdownModule(): Unregistered GameKit Project Settings"));
    }
}

TMap<FName, IGameKitEditorFeatureExample*> FAwsGameKitEditorModule::GetGameKitFeatureExamples() const
{
    return this->gamekitFeatureExamples;
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FAwsGameKitEditorModule, AwsGameKitEditor);