int32 BuildPatchToolMain( const TCHAR* CommandLine )
{
	// Initialize the command line
	FCommandLine::Set(CommandLine);

	// Add log devices
	if (FParse::Param(FCommandLine::Get(), TEXT("stdout")))
	{
		GLog->AddOutputDevice(new FBuildPatchOutputDevice());
	}
	if (FPlatformMisc::IsDebuggerPresent())
	{
		GLog->AddOutputDevice(new FOutputDeviceDebug());
	}

	GLog->Logf(TEXT("BuildPatchToolMain ran with: %s"), CommandLine);

	FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
	bool bSuccess = false;

	FString RootDirectory;
	FString CloudDirectory;
	uint32  AppID=0;
	FString AppName;
	FString BuildVersion;
	FString LaunchExe;
	FString LaunchCommand;
	FString IgnoreListFile;
	FString PrereqName;
	FString PrereqPath;
	FString PrereqArgs;
	FString ManifestsList;
	FString ManifestsFile;
	float DataAgeThreshold = 0.0f;
	FString IniFile;
	TMap<FString, FVariant> CustomFields;

	bool bCompactify = false;
	bool bPatchGeneration = true;
	bool bPreview = false;
	bool bPatchWithReuseAgeThreshold = true;

	// Collect all the info from the CommandLine
	TArray< FString > Tokens, Switches;
	FCommandLine::Parse(FCommandLine::Get(), Tokens, Switches);
	if (Switches.Num() > 0)
	{
		int32 BuildRootIdx;
		int32 CloudDirIdx;
		int32 AppIDIdx;
		int32 AppNameIdx;
		int32 BuildVersionIdx;
		int32 AppLaunchIdx;
		int32 AppArgsIdx;
		int32 FileIgnoreListIdx;
		int32 PrereqNameIdx;
		int32 PrereqPathIdx;
		int32 PrereqArgsIdx;
		int32 ManifestsListIdx;
		int32 ManifestsFileIdx;
		int32 DataAgeThresholdIdx;

		FCommandLineMatcher Matcher;
		bSuccess = true;

		Matcher.Command = TEXT("compactify");
		bCompactify = Switches.IndexOfByPredicate(Matcher) != INDEX_NONE;
		bPatchGeneration = !bCompactify;

		Matcher.Command = TEXT("preview");
		bPreview = bCompactify && Switches.IndexOfByPredicate(Matcher) != INDEX_NONE;
		
		Matcher.Command = TEXT( "BuildRoot" );
		BuildRootIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "CloudDir" );
		CloudDirIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "AppID" );
		AppIDIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "AppName" );
		AppNameIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "BuildVersion" );
		BuildVersionIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "AppLaunch" );
		AppLaunchIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "AppArgs" );
		AppArgsIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "FileIgnoreList" );
		FileIgnoreListIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "PrereqName" );
		PrereqNameIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "PrereqPath" );
		PrereqPathIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT( "PrereqArgs" );
		PrereqArgsIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT("ManifestsList");
		ManifestsListIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT("ManifestsFile");
		ManifestsFileIdx = Switches.IndexOfByPredicate(Matcher);

		Matcher.Command = TEXT("DataAgeThreshold");
		DataAgeThresholdIdx = Switches.IndexOfByPredicate(Matcher);

		// Check required param indexes
		bSuccess = bSuccess && CloudDirIdx != INDEX_NONE;
		if (bPatchGeneration)
		{
			bSuccess = bSuccess && BuildRootIdx != INDEX_NONE;
			bSuccess = bSuccess && AppIDIdx != INDEX_NONE;
			bSuccess = bSuccess && AppNameIdx != INDEX_NONE;
			bSuccess = bSuccess && BuildVersionIdx != INDEX_NONE;
			bSuccess = bSuccess && AppLaunchIdx != INDEX_NONE;
			bSuccess = bSuccess && AppArgsIdx != INDEX_NONE;
		}

		// Get required param values
		bSuccess = bSuccess && FParse::Value( *Switches[CloudDirIdx], TEXT( "CloudDir=" ), CloudDirectory );
		if (bPatchGeneration)
		{
			bSuccess = bSuccess && FParse::Value(*Switches[BuildRootIdx], TEXT("BuildRoot="), RootDirectory);
			bSuccess = bSuccess && FParse::Value(*Switches[AppIDIdx], TEXT("AppID="), AppID);
			bSuccess = bSuccess && FParse::Value(*Switches[AppNameIdx], TEXT("AppName="), AppName);
			bSuccess = bSuccess && FParse::Value(*Switches[BuildVersionIdx], TEXT("BuildVersion="), BuildVersion);
			bSuccess = bSuccess && FParse::Value(*Switches[AppLaunchIdx], TEXT("AppLaunch="), LaunchExe);
			bSuccess = bSuccess && FParse::Value(*Switches[AppArgsIdx], TEXT("AppArgs="), LaunchCommand);
		}

		// Get optional param values
		if( FileIgnoreListIdx != INDEX_NONE )
		{
			FParse::Value( *Switches[ FileIgnoreListIdx ], TEXT( "FileIgnoreList=" ), IgnoreListFile );
		}

		if( PrereqNameIdx != INDEX_NONE )
		{
			FParse::Value( *Switches[ PrereqNameIdx ], TEXT( "PrereqName=" ), PrereqName );
		}

		if( PrereqPathIdx != INDEX_NONE )
		{
			FParse::Value( *Switches[ PrereqPathIdx ], TEXT( "PrereqPath=" ), PrereqPath );
		}

		if( PrereqArgsIdx != INDEX_NONE )
		{
			FParse::Value( *Switches[ PrereqArgsIdx ], TEXT( "PrereqArgs=" ), PrereqArgs );
		}

		if (ManifestsListIdx != INDEX_NONE)
		{
			bool bShouldStopOnComma = false;
			FParse::Value(*Switches[ManifestsListIdx], TEXT("ManifestsList="), ManifestsList, bShouldStopOnComma);
		}
		else if (ManifestsFileIdx != INDEX_NONE)
		{
			FParse::Value( *Switches[ ManifestsFileIdx ], TEXT( "ManifestsFile=" ), ManifestsFile);
		}

		FString CustomValue;
		FString Left;
		FString Right;
		for (const auto& Switch : Switches)
		{
			if (FParse::Value(*Switch, TEXT("custom="), CustomValue))
			{
				if (CustomValue.Split(TEXT("="), &Left, &Right))
				{
					Left.Trim();
					Left.TrimTrailing();
					Right.Trim();
					Right.TrimTrailing();
					CustomFields.Add(Left, FVariant(Right));
				}
			}
			else if (FParse::Value(*Switch, TEXT("customfloat="), CustomValue))
			{
				if (CustomValue.Split(TEXT("="), &Left, &Right))
				{
					Left.Trim();
					Left.TrimTrailing();
					Right.Trim();
					Right.TrimTrailing();
					if (!Right.IsNumeric())
					{
						GLog->Log(ELogVerbosity::Error, TEXT("An error occurred processing token -customfloat. Non Numeric character found right of ="));
						bSuccess = false;
					}
					CustomFields.Add(Left, FVariant(TCString<TCHAR>::Atod(*Right)));
				}
			}
			else if (FParse::Value(*Switch, TEXT("customint="), CustomValue))
			{
				if (CustomValue.Split(TEXT("="), &Left, &Right))
				{
					Left.Trim();
					Left.TrimTrailing();
					Right.Trim();
					Right.TrimTrailing();
					if (!Right.IsNumeric())
					{
						GLog->Log(ELogVerbosity::Error, TEXT("An error occurred processing token -customint. Non Numeric character found right of ="));
						bSuccess = false;
					}
					CustomFields.Add(Left, FVariant(TCString<TCHAR>::Atoi64(*Right)));
				}
			}
		}

		FPaths::NormalizeDirectoryName( RootDirectory );
		FPaths::NormalizeDirectoryName( CloudDirectory );

		if (bSuccess)
		{
			// Initialize the configuration system, we can only do this reliably if we have CloudDirectory (i.e. bSuccess is true)
			IniFile = CloudDirectory / TEXT("BuildPatchTool.ini");
			GConfig->InitializeConfigSystem();
		}

		if (DataAgeThresholdIdx != INDEX_NONE)
		{
			FParse::Value(*Switches[DataAgeThresholdIdx], TEXT("DataAgeThreshold="), DataAgeThreshold);
		}
		else if (bSuccess && bCompactify)
		{
			// For compactification, if we don't pass in DataAgeThreshold, and it's not in BuildPatchTool.ini,
			// then we set it to zero, to indicate that any unused chunks are valid for deletion
			if (!GConfig->GetFloat(TEXT("Compactify"), TEXT("DataAgeThreshold"), DataAgeThreshold, IniFile))
			{
				GLog->Log(ELogVerbosity::Warning, TEXT("DataAgeThreshold not supplied, so all unreferenced data is eliglble for deletion. Note that this process is NOT compatible with any concurrently running patch generaiton processes"));
				DataAgeThreshold = 0.0f;
			}
		}
		else if (bSuccess && bPatchGeneration)
		{
			// For patch generation, if we don't pass in DataAgeThreshold, and it's not specified in BuildPatchTool.ini,
			// then we set bChunkWithReuseAgeThreshold to false, which indicates that *all* patch data is valid for reuse
			if (!GConfig->GetFloat(TEXT("PatchGeneration"), TEXT("DataAgeThreshold"), DataAgeThreshold, IniFile))
			{
				GLog->Log(ELogVerbosity::Warning, TEXT("DataAgeThreshold not supplied, so all existing data is eligible for reuse. Note that this process is NOT compatible with any concurrently running compactify processes"));
				DataAgeThreshold = 0.0f;
				bPatchWithReuseAgeThreshold = false;
			}
		}
	}

	// Check for argument error
	if( !bSuccess )
	{
		GLog->Log(ELogVerbosity::Error, TEXT("An error occurred processing arguments"));
		return 1;
	}


	// Initialize the file manager
	IFileManager::Get().ProcessCommandLineOptions();

	// Load the BuildPatchServices Module
	TSharedPtr<IBuildPatchServicesModule> BuildPatchServicesModule = StaticCastSharedPtr<IBuildPatchServicesModule>( FModuleManager::Get().LoadModule( TEXT( "BuildPatchServices" ) ) );

	// Initialise the UObject system and process our uobject classes
	FModuleManager::Get().LoadModule(TEXT("CoreUObject"));
	FCoreDelegates::OnInit.Broadcast();
	ProcessNewlyLoadedUObjects();

	// Setup the module
	BuildPatchServicesModule->SetCloudDirectory( CloudDirectory + TEXT( "/" ) );

	if (bCompactify)
	{
		// Split out our manifests to keep arg (if any) into an array of manifest filenames
		TArray<FString> ManifestsArr;
		if (ManifestsList.Len() > 0)
		{
			ManifestsList.ParseIntoArray(&ManifestsArr, TEXT(","), true);
		}
		else if (ManifestsFile.Len() > 0)
		{
			FString ManifestsFilePath = CloudDirectory / ManifestsFile;
			FString Temp;
			if (FFileHelper::LoadFileToString(Temp, *ManifestsFilePath))
			{
				Temp.ReplaceInline(TEXT("\r"), TEXT("\n"));
				Temp.ParseIntoArray(&ManifestsArr, TEXT("\n"), true);
			}
			else
			{
				GLog->Log(ELogVerbosity::Error, TEXT("Could not open specified manifests to keep file"));
				BuildPatchServicesModule.Reset();
				FCoreDelegates::OnExit.Broadcast();
				return 2;
			}
		}

		// Run the compactify routine
		bSuccess = BuildPatchServicesModule->CompactifyCloudDirectory(ManifestsArr, DataAgeThreshold, bPreview);
	}
	else if (bPatchGeneration)
	{
		FBuildPatchSettings Settings;
		Settings.RootDirectory = RootDirectory + TEXT("/");
		Settings.AppID = AppID;
		Settings.AppName = AppName;
		Settings.BuildVersion = BuildVersion;
		Settings.LaunchExe = LaunchExe;
		Settings.LaunchCommand = LaunchCommand;
		Settings.IgnoreListFile = IgnoreListFile;
		Settings.PrereqName = PrereqName;
		Settings.PrereqPath = PrereqPath;
		Settings.PrereqArgs = PrereqArgs;
		Settings.DataAgeThreshold = DataAgeThreshold;
		Settings.bShouldHonorReuseThreshold = bPatchWithReuseAgeThreshold;
		Settings.CustomFields = CustomFields;

		// Run the build generation
		if (FParse::Param(FCommandLine::Get(), TEXT("nochunks")))
		{
			bSuccess = BuildPatchServicesModule->GenerateFilesManifestFromDirectory(Settings);
		}
		else
		{
			bSuccess = BuildPatchServicesModule->GenerateChunksManifestFromDirectory(Settings);
		}
	}
	else
	{
		GLog->Log(ELogVerbosity::Error, TEXT("Unknown tool mode"));
		BuildPatchServicesModule.Reset();
		FCoreDelegates::OnExit.Broadcast();
		return 3;
	}

	// Release the module ptr
	BuildPatchServicesModule.Reset();

	// Check for processing error
	if (!bSuccess)
	{
		GLog->Log(ELogVerbosity::Error, TEXT("A fatal error occurred executing BuildPatchTool.exe"));
		FCoreDelegates::OnExit.Broadcast();
		return 4;
	}

	FCoreDelegates::OnExit.Broadcast();

	GLog->Log(TEXT("BuildPatchToolMain completed successfuly"));
	return 0;
}
int RunSlateViewer( const TCHAR* CommandLine )
{
	// start up the main loop
	GEngineLoop.PreInit(CommandLine);

	// Make sure all UObject classes are registered and default properties have been initialized
	ProcessNewlyLoadedUObjects();
	
	// Tell the module manager is may now process newly-loaded UObjects when new C++ modules are loaded
	FModuleManager::Get().StartProcessingNewlyLoadedObjects();

	// crank up a normal Slate application using the platform's standalone renderer
	FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer());

	// Load the source code access module
	ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>( FName( "SourceCodeAccess" ) );
	
	// Manually load in the source code access plugins, as standalone programs don't currently support plugins.
#if PLATFORM_MAC
	IModuleInterface& XCodeSourceCodeAccessModule = FModuleManager::LoadModuleChecked<IModuleInterface>( FName( "XCodeSourceCodeAccess" ) );
	SourceCodeAccessModule.SetAccessor(FName("XCodeSourceCodeAccess"));
#elif PLATFORM_WINDOWS
	IModuleInterface& VisualStudioSourceCodeAccessModule = FModuleManager::LoadModuleChecked<IModuleInterface>( FName( "VisualStudioSourceCodeAccess" ) );
	SourceCodeAccessModule.SetAccessor(FName("VisualStudioSourceCodeAccess"));
#endif

	// set the application name
	FGlobalTabmanager::Get()->SetApplicationTitle(LOCTEXT("AppTitle", "Slate Viewer"));
	FModuleManager::LoadModuleChecked<ISlateReflectorModule>("SlateReflector").RegisterTabSpawner(WorkspaceMenu::DeveloperMenu);

	FGlobalTabmanager::Get()->RegisterNomadTabSpawner("WebBrowserTab", FOnSpawnTab::CreateStatic(&SpawnWebBrowserTab))
		.SetDisplayName(LOCTEXT("WebBrowserTab", "Web Browser"));
	
	if (FParse::Param(FCommandLine::Get(), TEXT("perftest")))
	{
		// Bring up perf test
		SummonPerfTestSuite();
	}
	else
	{
		// Bring up the test suite.
		RestoreSlateTestSuite();
	}


#if WITH_SHARED_POINTER_TESTS
	SharedPointerTesting::TestSharedPointer<ESPMode::Fast>();
	SharedPointerTesting::TestSharedPointer<ESPMode::ThreadSafe>();
#endif

	// loop while the server does the rest
	while (!GIsRequestingExit)
	{
		FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
		FStats::AdvanceFrame(false);
		FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime());
		FSlateApplication::Get().PumpMessages();
		FSlateApplication::Get().Tick();		
		FPlatformProcess::Sleep(0);
	}
	FModuleManager::Get().UnloadModulesAtShutdown();
	FSlateApplication::Shutdown();

	return 0;
}