// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2025.

#include "FMODSettings.h"
#include "Misc/Paths.h"

#if WITH_EDITOR
#include "Settings/ProjectPackagingSettings.h"
#include <ObjectTools.h>
#endif

#ifdef FMOD_PLATFORM_HEADER
#include "FMODPlatform.h"
#endif

//////////////////////////////////////////////////////////////////////////
// UPaperRuntimeSettings

inline EFMODPlatforms::Type CurrentPlatform()
{
    EFMODPlatforms::Type platform;
#if defined(FMOD_PLATFORM_HEADER)
    platform = FMODPlatform_CurrentPlatform();
#elif WITH_EDITOR
    platform = EFMODPlatforms::Editor;
#elif PLATFORM_WINDOWS
    platform = EFMODPlatforms::Windows;
#elif PLATFORM_LINUX
    platform = EFMODPlatforms::Linux;
#elif PLATFORM_MAC
    platform = EFMODPlatforms::Mac;
#elif PLATFORM_ANDROID
    platform = EFMODPlatforms::Android;
#elif PLATFORM_IOS || PLATFORM_TVOS
    platform = EFMODPlatforms::IOS;
#endif
    return platform;
}

UFMODSettings::UFMODSettings(const FObjectInitializer &ObjectInitializer)
    : Super(ObjectInitializer)
    , bLoadAllBanks(true)
    , bLoadAllSampleData(false)
    , bEnableLiveUpdate(true)
    , bEnableEditorLiveUpdate(false)
    , OutputFormat(EFMODSpeakerMode::Surround_5_1)
    , OutputType(EFMODOutput::TYPE_AUTODETECT)
    , Vol0VirtualLevel(0.001f)
    , SampleRate(0)
    , bMatchHardwareSampleRate(true)
    , RealChannelCount(64)
    , TotalChannelCount(512)
    , DSPBufferLength(0)
    , DSPBufferCount(0)
    , FileBufferSize(2048)
    , StudioUpdatePeriod(0)
    , bLockAllBuses(false)
    , LiveUpdatePort(9264)
    , EditorLiveUpdatePort(9265)
    , ReloadBanksDelay(5)
    , bEnableAPIErrorLogging(false)
    , bEnableMemoryTracking(false)
    , ContentBrowserPrefix(TEXT("/Game/FMOD/"))
    , MasterBankName(TEXT("Master"))
    , LoggingLevel(LEVEL_WARNING)
    , bFMODAudioLinkEnabled(false)
{
    BankOutputDirectory.Path = TEXT("FMOD");
}

FString UFMODSettings::GetFullBankPath() const
{
    FString FullPath = BankOutputDirectory.Path;
    if (FPaths::IsRelative(FullPath))
    {
        FullPath = FPaths::ProjectContentDir() / FullPath;
    }

    if (ForcePlatformName == TEXT("."))
    {
        // Leave path without subdirectory
    }
    else if (!ForcePlatformName.IsEmpty())
    {
        FullPath = FullPath / ForcePlatformName;
    }
    else
    {
#ifdef FMOD_PLATFORM_HEADER
        FString PlatformName = FMODPlatform_PlatformName();
#elif PLATFORM_IOS || PLATFORM_TVOS || PLATFORM_ANDROID
        FString PlatformName = "Mobile";
#else
        FString PlatformName = "Desktop";
#endif
        FullPath = FullPath / PlatformName;
    }
    return FullPath;
}

FString UFMODSettings::GetMasterBankFilename() const
{
    return MasterBankName + TEXT(".bank");
}

FString UFMODSettings::GetMasterAssetsBankFilename() const
{
    return MasterBankName + TEXT(".assets.bank");
}

FString UFMODSettings::GetMasterStringsBankFilename() const
{
    return MasterBankName + TEXT(".strings.bank");
}

FString UFMODSettings::GetFullContentPath() const
{
    return ContentBrowserPrefix;
}

#if WITH_EDITOR
FString UFMODSettings::GetDesktopBankPath() const
{
    FString Path = BankOutputDirectory.Path;

    if (ForcePlatformName.IsEmpty())
    {
        Path = Path / "Desktop";
    }
    else if (ForcePlatformName != TEXT("."))
    {
        Path = Path / ForcePlatformName;
    }

    return Path;
}

UFMODSettings::EProblem UFMODSettings::Check() const
{
    if (!IsBankPathSet())
    {
        return BankPathNotSet;
    }

    // Check packaging settings to ensure that only the correct bank output directory for desktop (or forced platform) banks is set-up for staging
    FString DesktopBankPath = GetDesktopBankPath();
    UProjectPackagingSettings* PackagingSettings = Cast<UProjectPackagingSettings>(UProjectPackagingSettings::StaticClass()->GetDefaultObject());
    bool bCorrectPathAdded = false;
    bool bOtherPathsAdded = false;
    bool bAssetsToCookAdded = false;

    for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysStageAsNonUFS.Num(); ++i)
    {
        if (PackagingSettings->DirectoriesToAlwaysStageAsNonUFS[i].Path.StartsWith(BankOutputDirectory.Path))
        {
            if (PackagingSettings->DirectoriesToAlwaysStageAsNonUFS[i].Path == DesktopBankPath)
            {
                bCorrectPathAdded = true;
            }
            else
            {
                bOtherPathsAdded = true;
            }
        }
    }

    for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysStageAsUFS.Num(); ++i)
    {
        if (PackagingSettings->DirectoriesToAlwaysStageAsUFS[i].Path.StartsWith(BankOutputDirectory.Path))
        {
            bOtherPathsAdded = true;
            break;
        }
    }

    for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysCook.Num(); ++i)
    {
        if (PackagingSettings->DirectoriesToAlwaysCook[i].Path.StartsWith(GetFullContentPath()))
        {
            bAssetsToCookAdded = true;
            break;
        }
    }

    if (!bCorrectPathAdded || bOtherPathsAdded || !bAssetsToCookAdded)
    {
        return PackagingSettingsBad;
    }

    return Okay;
}

void UFMODSettings::PostEditChangeProperty(FPropertyChangedEvent& e)
{
    FName PropertyName = (e.Property != NULL) ? e.Property->GetFName() : NAME_None;
    // Validate ContentBrowserPrefix, as Unreal can crash if the prefix is improperly configured 
    if (PropertyName == GET_MEMBER_NAME_CHECKED(UFMODSettings, ContentBrowserPrefix))
    {
        FStrProperty* prop = CastField<FStrProperty>(e.Property);
        void* propertyAddress = e.Property->ContainerPtrToValuePtr<void>(this);
        FString contentBrowserPrefix = prop->GetPropertyValue(propertyAddress);

        // Check for empty prefix
        if (contentBrowserPrefix.IsEmpty()) {
            contentBrowserPrefix = "/";
        }
        else {

            // FName's max length is 1023, but FMOD needs to append additional directories
            // 512 is an arbitary length that should cover most prefix lengths
            const int ContentBrowserPrefixMaxLength = 512;

            // Ensure that length doesn't exceed max prefix length
            if (contentBrowserPrefix.Len() > ContentBrowserPrefixMaxLength) {
                contentBrowserPrefix.LeftChopInline(ContentBrowserPrefixMaxLength);
            }

            // Remove invalid long package characters
            contentBrowserPrefix = ObjectTools::SanitizeInvalidChars(contentBrowserPrefix, INVALID_LONGPACKAGE_CHARACTERS);

            // Remove double slashes
            int32 index = contentBrowserPrefix.Find(FString("//"));
            while (index != INDEX_NONE) {
                contentBrowserPrefix.RemoveAt(index);
                index = contentBrowserPrefix.Find(FString("//"));
            }

            // Check for starting and ending with slash
            if (!contentBrowserPrefix.StartsWith("/")) {
                contentBrowserPrefix = "/" + contentBrowserPrefix;
            }
            if (!contentBrowserPrefix.EndsWith("/")) {
                contentBrowserPrefix += "/";
            }
        }

        prop->SetPropertyValue(propertyAddress, contentBrowserPrefix);

    }
    Super::PostEditChangeProperty(e);
}
#endif // WITH_EDITOR

EFMODSpeakerMode::Type UFMODSettings::GetSpeakerMode() const
{
    return Platforms.Contains(CurrentPlatform()) ? Platforms.Find(CurrentPlatform())->SpeakerMode : OutputFormat;
}

EFMODOutput::Type UFMODSettings::GetOutputType() const
{
    return Platforms.Contains(CurrentPlatform()) ? Platforms.Find(CurrentPlatform())->OutputType : OutputType;
}

int32 UFMODSettings::GetSampleRate() const
{
    return Platforms.Contains(CurrentPlatform()) ? Platforms.Find(CurrentPlatform())->SampleRate : SampleRate;
}

int32 UFMODSettings::GetMemoryPoolSize() const
{
    return (Platforms.Contains(CurrentPlatform()) ? Platforms.Find(CurrentPlatform())->CustomPoolSize : MemoryPoolSize);
}

int32 UFMODSettings::GetRealChannelCount() const
{
    return Platforms.Contains(CurrentPlatform()) ? Platforms.Find(CurrentPlatform())->RealChannelCount : RealChannelCount;
}

TMap<TEnumAsByte<EFMODCodec::Type>, int32> UFMODSettings::GetCodecs() const
{
    return Platforms.Contains(CurrentPlatform()) ? Platforms.Find(CurrentPlatform())->Codecs : Codecs;
}