// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
//
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
// or copy at http://opensource.org/licenses/MIT)

#include "SGitSourceControlSettings.h"

#include "Runtime/Launch/Resources/Version.h"
#include "Fonts/SlateFontInfo.h"
#include "Misc/App.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SFilePathPicker.h"
#include "Widgets/Input/SMultiLineEditableTextBox.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Framework/Notifications/NotificationManager.h"
#include "EditorDirectories.h"
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
#else
#include "EditorStyleSet.h"
#endif
#include "SourceControlOperations.h"
#include "GitSourceControlModule.h"
#include "GitSourceControlUtils.h"


#define LOCTEXT_NAMESPACE "SGitSourceControlSettings"

void SGitSourceControlSettings::Construct(const FArguments& InArgs)
{
	bAutoCreateGitIgnore = true;
	bAutoCreateReadme = true;
	bAutoCreateGitAttributes = false;
	bAutoInitialCommit = true;

	InitialCommitMessage = LOCTEXT("InitialCommitMessage", "Initial commit");
	ReadmeContent = FText::FromString(FString(TEXT("# ")) + FApp::GetProjectName() + "\n\nDeveloped with Unreal Engine\n");

	ConstructBasedOnEngineVersion( );
}

#if ENGINE_MAJOR_VERSION < 5
void SGitSourceControlSettings::ConstructBasedOnEngineVersion( )
{
	const FText FileFilterType = NSLOCTEXT("GitSourceControl", "Executables", "Executables");
#if PLATFORM_WINDOWS
	const FString FileFilterText = FString::Printf(TEXT("%s (*.exe)|*.exe"), *FileFilterType.ToString());
#else
	const FString FileFilterText = FString::Printf(TEXT("%s"), *FileFilterType.ToString());
#endif

	const FSlateFontInfo Font = FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"));
	ChildSlot
	[
		SNew(SBorder)
		.BorderImage( FEditorStyle::GetBrush("DetailsView.CategoryBottom"))
		.Padding(FMargin(0.0f, 3.0f, 0.0f, 0.0f))
		[
			SNew(SVerticalBox)
			// Path to the Git command line executable
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.ToolTipText(LOCTEXT("BinaryPathLabel_Tooltip", "Path to Git binary"))
				+SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("BinaryPathLabel", "Git Path"))
					.Font(Font)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.0f)
				[
					SNew(SFilePathPicker)
					.BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
					.BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
					.BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN))
					.BrowseTitle(LOCTEXT("BinaryPathBrowseTitle", "File picker..."))
					.FilePath(this, &SGitSourceControlSettings::GetBinaryPathString)
					.FileTypeFilter(FileFilterText)
					.OnPathPicked(this, &SGitSourceControlSettings::OnBinaryPathPicked)
				]
			]
			// Root of the local repository
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.ToolTipText(LOCTEXT("RepositoryRootLabel_Tooltip", "Path to the root of the Git repository"))
				+SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("RepositoryRootLabel", "Root of the repository"))
					.Font(Font)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.0f)
				[
					SNew(STextBlock)
					.Text(this, &SGitSourceControlSettings::GetPathToRepositoryRoot)
					.Font(Font)
				]
			]
			// User Name
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.ToolTipText(LOCTEXT("GitUserName_Tooltip", "User name configured for the Git repository"))
				+SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("GitUserName", "User Name"))
					.Font(Font)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.0f)
				[
					SNew(STextBlock)
					.Text(this, &SGitSourceControlSettings::GetUserName)
					.Font(Font)
				]
			]
			// User e-mail
			+SVerticalBox::Slot()
			.FillHeight(1.0f)
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.ToolTipText(LOCTEXT("GitUserEmail_Tooltip", "User e-mail configured for the Git repository"))
				+SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("GitUserEmail", "E-Mail"))
					.Font(Font)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.0f)
				[
					SNew(STextBlock)
					.Text(this, &SGitSourceControlSettings::GetUserEmail)
					.Font(Font)
				]
			]
			// Separator
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SSeparator)
			]
			// Explanation text
			+SVerticalBox::Slot()
			.FillHeight(1.0f)
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				+SHorizontalBox::Slot()
				.FillWidth(1.0f)
				.HAlign(HAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("RepositoryNotFound", "Current Project is not contained in a Git Repository. Fill the form below to initialize a new Repository."))
					.ToolTipText(LOCTEXT("RepositoryNotFound_Tooltip", "No Repository found at the level or above the current Project"))
					.Font(Font)
				]
			]
			// Option to configure the URL of the default remote 'origin'
			// TODO: option to configure the name of the remote instead of the default origin
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				.ToolTipText(LOCTEXT("ConfigureOrigin_Tooltip", "Configure the URL of the default remote 'origin'"))
				+SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("ConfigureOrigin", "URL of the remote server 'origin'"))
					.Font(Font)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.0f)
				.VAlign(VAlign_Center)
				[
					SNew(SEditableTextBox)
					.Text(this, &SGitSourceControlSettings::GetRemoteUrl)
					.OnTextCommitted(this, &SGitSourceControlSettings::OnRemoteUrlCommited)
					.Font(Font)
				]
			]
			// Option to add a proper .gitignore file (true by default)
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				.ToolTipText(LOCTEXT("CreateGitIgnore_Tooltip", "Create and add a standard '.gitignore' file"))
				+SHorizontalBox::Slot()
				.FillWidth(0.1f)
				[
					SNew(SCheckBox)
					.IsChecked(ECheckBoxState::Checked)
					.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateGitIgnore)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.9f)
				.VAlign(VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("CreateGitIgnore", "Add a .gitignore file"))
					.Font(Font)
				]
			]
			// Option to add a README.md file with custom content
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				.ToolTipText(LOCTEXT("CreateReadme_Tooltip", "Add a README.md file"))
				+SHorizontalBox::Slot()
				.FillWidth(0.1f)
				[
					SNew(SCheckBox)
					.IsChecked(ECheckBoxState::Checked)
					.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateReadme)
				]
				+SHorizontalBox::Slot()
				.FillWidth(0.9f)
				.VAlign(VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("CreateReadme", "Add a basic README.md file"))
					.Font(Font)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.0f)
				.Padding(2.0f)
				[
					SNew(SMultiLineEditableTextBox)
					.Text(this, &SGitSourceControlSettings::GetReadmeContent)
					.OnTextCommitted(this, &SGitSourceControlSettings::OnReadmeContentCommited)
					.IsEnabled(this, &SGitSourceControlSettings::GetAutoCreateReadme)
					.SelectAllTextWhenFocused(true)
					.Font(Font)
				]
			]
			// Option to add a proper .gitattributes file for Git LFS (false by default)
			+SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2.0f)
			.VAlign(VAlign_Center)
			[
				SNew(SHorizontalBox)
				.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				.ToolTipText(LOCTEXT("CreateGitAttributes_Tooltip", "Create and add a '.gitattributes' file to enable Git LFS for the whole 'Content/' directory (needs Git LFS extensions to be installed)."))
				+SHorizontalBox::Slot()
				.FillWidth(0.1f)
				[
					SNew(SCheckBox)
					.IsChecked(ECheckBoxState::Unchecked)
					.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateGitAttributes)
				]
				+SHorizontalBox::Slot()
				.FillWidth(2.9f)
				.VAlign(VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("CreateGitAttributes", "Add a .gitattributes file to enable Git LFS"))
					.Font(Font)
				]
			]
			// Option to use the Git LFS File Locking workflow (false by default)
			// Enabled even after init to switch it off in case of no network
			// TODO LFS turning it off afterwards does not work because all files are readonly !
			+SVerticalBox::Slot()
				.AutoHeight()
				.Padding(2.0f)
				.VAlign(VAlign_Center)
				[
					SNew(SHorizontalBox)
					.ToolTipText(LOCTEXT("UseGitLfsLocking_Tooltip", "Uses Git LFS 2 File Locking workflow (CheckOut and Commit/Push)."))
				+ SHorizontalBox::Slot()
				.FillWidth(0.1f)
				[
					SNew(SCheckBox)
					.IsChecked(SGitSourceControlSettings::IsUsingGitLfsLocking())
				.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedUseGitLfsLocking)
				.IsEnabled(this, &SGitSourceControlSettings::CanUseGitLfsLocking)
				]
			+ SHorizontalBox::Slot()
				.FillWidth(0.9f)
				.VAlign(VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("UseGitLfsLocking", "Uses Git LFS 2 File Locking workflow"))
				.Font(Font)
				]
			// Username credential used to access the Git LFS 2 File Locks server
			+ SHorizontalBox::Slot()
				.FillWidth(2.0f)
				.VAlign(VAlign_Center)
				[
					SNew(SEditableTextBox)
					.Text(this, &SGitSourceControlSettings::GetLfsUserName)
				.OnTextCommitted(this, &SGitSourceControlSettings::OnLfsUserNameCommited)
				.IsEnabled(this, &SGitSourceControlSettings::GetIsUsingGitLfsLocking)
				.HintText(LOCTEXT("LfsUserName_Hint", "Username to lock files on the LFS server"))
				.Font(Font)
				]
				]
			// Option to Make the initial Git commit with custom message
			+ SVerticalBox::Slot()
				.AutoHeight()
				.Padding(2.0f)
				.VAlign(VAlign_Center)
				[
					SNew(SHorizontalBox)
					.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				.ToolTipText(LOCTEXT("InitialGitCommit_Tooltip", "Make the initial Git commit"))
				+ SHorizontalBox::Slot()
				.FillWidth(0.1f)
				[
					SNew(SCheckBox)
					.IsChecked(ECheckBoxState::Checked)
				.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedInitialCommit)
				]
			+ SHorizontalBox::Slot()
				.FillWidth(0.9f)
				.VAlign(VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("InitialGitCommit", "Make the initial Git commit"))
				.Font(Font)
				]
			+ SHorizontalBox::Slot()
				.FillWidth(2.0f)
				.Padding(2.0f)
				[
					SNew(SMultiLineEditableTextBox)
					.Text(this, &SGitSourceControlSettings::GetInitialCommitMessage)
				.OnTextCommitted(this, &SGitSourceControlSettings::OnInitialCommitMessageCommited)
				.IsEnabled(this, &SGitSourceControlSettings::GetAutoInitialCommit)
				.SelectAllTextWhenFocused(true)
				.Font(Font)
				]
				]
			// Button to initialize the project with Git, create .gitignore/.gitattributes files, and make the first commit)
			+ SVerticalBox::Slot()
				.FillHeight(2.5f)
				.Padding(4.0f)
				.VAlign(VAlign_Center)
				[
					SNew(SHorizontalBox)
					.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
				+ SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SNew(SButton)
					.Text(LOCTEXT("GitInitRepository", "Initialize project with Git"))
				.ToolTipText(LOCTEXT("GitInitRepository_Tooltip", "Initialize current project as a new Git repository"))
				.OnClicked(this, &SGitSourceControlSettings::OnClickedInitializeGitRepository)
				.IsEnabled(this, &SGitSourceControlSettings::CanInitializeGitRepository)
				.HAlign(HAlign_Center)
				.ContentPadding(6)
				]
				]
		]
	];
}
#else
void SGitSourceControlSettings::ConstructBasedOnEngineVersion( )
{
	const FText FileFilterType = NSLOCTEXT("GitSourceControl", "Executables", "Executables");
#if PLATFORM_WINDOWS
	const FString FileFilterText = FString::Printf(TEXT("%s (*.exe)|*.exe"), *FileFilterType.ToString());
#else
	const FString FileFilterText = FString::Printf(TEXT("%s"), *FileFilterType.ToString());
#endif

	using Self = std::remove_pointer_t<decltype(this)>;

	#define ROW_LEFT( PADDING_HEIGHT ) +SHorizontalBox::Slot() \
			.VAlign(VAlign_Center) \
			.HAlign(HAlign_Right) \
			.FillWidth(1.0f) \
			.Padding(FMargin(0.0f, 0.0f, 16.0f, PADDING_HEIGHT))

	#define ROW_RIGHT( PADDING_HEIGHT ) +SHorizontalBox::Slot() \
			.VAlign(VAlign_Center) \
			.FillWidth(2.0f) \
			.Padding(FMargin(0.0f, 0.0f, 0.0f, PADDING_HEIGHT))

	#define TT_GitPath LOCTEXT("BinaryPathLabel_Tooltip", "Path to Git binary")
	#define TT_RepoRoot LOCTEXT("RepositoryRootLabel_Tooltip", "Path to the root of the Git repository")
	#define TT_UserName LOCTEXT("UserNameLabel_Tooltip", "Git Username fetched from local config")
	#define TT_Email LOCTEXT("GitUserEmail_Tooltip", "Git E-mail fetched from local config")
	#define TT_LFS LOCTEXT("UseGitLfsLocking_Tooltip", "Uses Git LFS 2 File Locking workflow (CheckOut and Commit/Push).")

	ChildSlot
	[
		SNew(SVerticalBox)
		// Git Path
		+SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SHorizontalBox)
			ROW_LEFT( 10.0f )
			[
				SNew(STextBlock)
				.Text(LOCTEXT("BinaryPathLabel", "Git Path"))
				.ToolTipText( TT_GitPath )
			]
			ROW_RIGHT( 10.0f )
			[
				SNew(SFilePathPicker)
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
				.BrowseButtonImage(FAppStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
				.BrowseButtonStyle(FAppStyle::Get(), "HoverHintOnly")
#else
				.BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
				.BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
#endif
				.BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN))
				.BrowseTitle(LOCTEXT("BinaryPathBrowseTitle", "File picker..."))
				.FilePath(this, &Self::GetBinaryPathString)
				.FileTypeFilter(FileFilterText)
				.OnPathPicked(this, &Self::OnBinaryPathPicked)
			]
		]
		// Repository Root
		+SVerticalBox::Slot()
		[
			SNew(SHorizontalBox)
			ROW_LEFT( 10.0f )
			[
				SNew(STextBlock)
				.Text(LOCTEXT("RepositoryRootLabel", "Root of the repository"))
				.ToolTipText( TT_RepoRoot )
			]
			ROW_RIGHT( 10.0f )
			[
				SNew(STextBlock)
				.Text(this, &Self::GetPathToRepositoryRoot)
				.ToolTipText( TT_RepoRoot )
			]
		]
		// User Name
		+SVerticalBox::Slot()
		[
			SNew(SHorizontalBox)
			ROW_LEFT( 10.0f )
			[
				SNew(STextBlock)
				.Text(LOCTEXT("UserNameLabel", "User Name"))
				.ToolTipText( TT_UserName )
			]
			ROW_RIGHT( 10.0f )
			[
				SNew(STextBlock)
				.Text(this, &Self::GetUserName)
				.ToolTipText( TT_UserName )
			]
		]
		// Email
		+SVerticalBox::Slot()
		[
			SNew(SHorizontalBox)
			ROW_LEFT( 10.0f )
			[
				SNew(STextBlock)
				.Text(LOCTEXT("EmailLabel", "E-mail"))
				.ToolTipText( TT_Email )
			]
			ROW_RIGHT( 10.0f )
			[
				SNew(STextBlock)
				.Text(this, &Self::GetUserEmail )
				.ToolTipText( TT_Email )
			]
		]
		// LFS Config
		+SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SHorizontalBox)
			ROW_LEFT( 10.0f )
			[
				SNew(SCheckBox)
				.IsChecked(Self::IsUsingGitLfsLocking())
				.OnCheckStateChanged(this, &Self::OnCheckedUseGitLfsLocking)
				.IsEnabled(this, &Self::CanUseGitLfsLocking)
				.Content()
				[
					SNew(STextBlock)
					.Text(LOCTEXT("UseGitLfsLocking", "Uses Git LFS"))
					.ToolTipText( TT_LFS )
				]
			]
			ROW_RIGHT( 10.0f )
			[
				SNew(SEditableTextBox)
				.Text(this, &Self::GetLfsUserName)
				.OnTextCommitted(this, &Self::OnLfsUserNameCommited)
				.IsEnabled(this, &Self::GetIsUsingGitLfsLocking)
				.HintText(LOCTEXT("LfsUserName_Hint", "Username to lock files on the LFS server"))
			]
		]
		// [Optional] Initial Git Commit
		+SVerticalBox::Slot()
		.AutoHeight()
		.Padding(2.0f)
		.VAlign(VAlign_Center)
		[
			SNew(SHorizontalBox)
			.ToolTipText(LOCTEXT("InitialGitCommit_Tooltip", "Make the initial Git commit"))
			.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
			+ SHorizontalBox::Slot()
			.FillWidth(0.1f)
			[
				SNew(SCheckBox)
				.IsChecked(ECheckBoxState::Checked)
				.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedInitialCommit)
			]
			+ SHorizontalBox::Slot()
			.FillWidth(0.9f)
			.VAlign(VAlign_Center)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("InitialGitCommit", "Make the initial Git commit"))
			]
			+ SHorizontalBox::Slot()
			.FillWidth(2.0f)
			.Padding(2.0f)
			[
				SNew(SMultiLineEditableTextBox)
				.Text(this, &SGitSourceControlSettings::GetInitialCommitMessage)
				.OnTextCommitted(this, &SGitSourceControlSettings::OnInitialCommitMessageCommited)
				.IsEnabled(this, &SGitSourceControlSettings::GetAutoInitialCommit)
				.SelectAllTextWhenFocused(true)
			]
		]
		// [Optional] Initialize Project with Git
		+SVerticalBox::Slot()
		.FillHeight(2.5f)
		.Padding(4.0f)
		.VAlign(VAlign_Center)
		[
			SNew(SHorizontalBox)
			.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
			+ SHorizontalBox::Slot()
			.FillWidth(1.0f)
			[
				SNew(SButton)
				.Text(LOCTEXT("GitInitRepository", "Initialize project with Git"))
				.ToolTipText(LOCTEXT("GitInitRepository_Tooltip", "Initialize current project as a new Git repository"))
				.OnClicked(this, &SGitSourceControlSettings::OnClickedInitializeGitRepository)
				.IsEnabled(this, &SGitSourceControlSettings::CanInitializeGitRepository)
				.HAlign(HAlign_Center)
				.ContentPadding(6)
			]
		]
	];

	// TODO [RW] The UE5 GUI for the two optional initial git support functionalities has not been tested
}
#endif

SGitSourceControlSettings::~SGitSourceControlSettings()
{
	RemoveInProgressNotification();
}

FString SGitSourceControlSettings::GetBinaryPathString() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	return GitSourceControl.AccessSettings().GetBinaryPath();
}

void SGitSourceControlSettings::OnBinaryPathPicked( const FString& PickedPath ) const
{
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	FString PickedFullPath = FPaths::ConvertRelativePathToFull(PickedPath);
	const bool bChanged = GitSourceControl.AccessSettings().SetBinaryPath(PickedFullPath);
	if(bChanged)
	{
		// Re-Check provided git binary path for each change
		GitSourceControl.GetProvider().CheckGitAvailability();
		if(GitSourceControl.GetProvider().IsGitAvailable())
		{
			GitSourceControl.SaveSettings();
		}
	}
}

FText SGitSourceControlSettings::GetPathToRepositoryRoot() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const FString& PathToRepositoryRoot = GitSourceControl.GetProvider().GetPathToRepositoryRoot();
	return FText::FromString(PathToRepositoryRoot);
}

FText SGitSourceControlSettings::GetUserName() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const FString& UserName = GitSourceControl.GetProvider().GetUserName();
	return FText::FromString(UserName);
}

FText SGitSourceControlSettings::GetUserEmail() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const FString& UserEmail = GitSourceControl.GetProvider().GetUserEmail();
	return FText::FromString(UserEmail);
}

EVisibility SGitSourceControlSettings::MustInitializeGitRepository() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const bool bGitAvailable = GitSourceControl.GetProvider().IsGitAvailable();
	const bool bGitRepositoryFound = GitSourceControl.GetProvider().IsEnabled();
#if 0
	return (bGitAvailable && !bGitRepositoryFound) ? EVisibility::Visible : EVisibility::Collapsed;
#else
	return EVisibility::Collapsed;
#endif
}

bool SGitSourceControlSettings::CanInitializeGitRepository() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const bool bGitAvailable = GitSourceControl.GetProvider().IsGitAvailable();
	const bool bGitRepositoryFound = GitSourceControl.GetProvider().IsEnabled();
	const FString& LfsUserName = GitSourceControl.AccessSettings().GetLfsUserName();
	const bool bIsUsingGitLfsLocking = GitSourceControl.GetProvider().UsesCheckout();
	const bool bGitLfsConfigOk = !bIsUsingGitLfsLocking || !LfsUserName.IsEmpty();
	const bool bInitialCommitConfigOk = !bAutoInitialCommit || !InitialCommitMessage.IsEmpty();
#if 0
	return (bGitAvailable && !bGitRepositoryFound && bGitLfsConfigOk && bInitialCommitConfigOk);
#else
	return false;
#endif
}

bool SGitSourceControlSettings::CanUseGitLfsLocking() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	// TODO LFS SRombauts : check if .gitattributes file is present and if Content/ is already tracked!
	const bool bGitAttributesCreated = true;
	return (bAutoCreateGitAttributes || bGitAttributesCreated);
}

FReply SGitSourceControlSettings::OnClickedInitializeGitRepository()
{
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
	const FString PathToProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
	TArray<FString> InfoMessages;
	TArray<FString> ErrorMessages;

	// 1.a. Synchronous (very quick) "git init" operation: initialize a Git local repository with a .git/ subdirectory
	GitSourceControlUtils::RunCommand(TEXT("init"), PathToGitBinary, PathToProjectDir, FGitSourceControlModule::GetEmptyStringArray(), FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
	// 1.b. Synchronous (very quick) "git remote add" operation: configure the URL of the default remote server 'origin' if specified
	if(!RemoteUrl.IsEmpty())
	{
		TArray<FString> Parameters;
		Parameters.Add(TEXT("add origin"));
		Parameters.Add(RemoteUrl.ToString());
		GitSourceControlUtils::RunCommand(TEXT("remote"), PathToGitBinary, PathToProjectDir, Parameters, FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
	}

	// Check the new repository status to enable connection (branch, user e-mail)
	GitSourceControl.GetProvider().CheckGitAvailability();
	if(GitSourceControl.GetProvider().IsAvailable())
	{
		// List of files to add to Revision Control (.uproject, Config/, Content/, Source/ files and .gitignore/.gitattributes if any)
		TArray<FString> ProjectFiles;
		ProjectFiles.Add(FPaths::ProjectContentDir());
		ProjectFiles.Add(FPaths::ProjectConfigDir());
		ProjectFiles.Add(FPaths::GetProjectFilePath());
		if (FPaths::DirectoryExists(FPaths::GameSourceDir()))
		{
			ProjectFiles.Add(FPaths::GameSourceDir());
		}
		if(bAutoCreateGitIgnore)
		{
			// 2.a. Create a standard ".gitignore" file with common patterns for a typical Blueprint & C++ project
			const FString GitIgnoreFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT(".gitignore"));
			const FString GitIgnoreContent = TEXT("Binaries\nDerivedDataCache\nIntermediate\nSaved\n.vscode\n.vs\n*.VC.db\n*.opensdf\n*.opendb\n*.sdf\n*.sln\n*.suo\n*.xcodeproj\n*.xcworkspace\n*.log");
			if(FFileHelper::SaveStringToFile(GitIgnoreContent, *GitIgnoreFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
			{
				ProjectFiles.Add(GitIgnoreFilename);
			}
		}
		if(bAutoCreateReadme)
		{
			// 2.b. Create a "README.md" file with a custom description
			const FString ReadmeFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT("README.md"));
			if (FFileHelper::SaveStringToFile(ReadmeContent.ToString(), *ReadmeFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
			{
				ProjectFiles.Add(ReadmeFilename);
			}
		}
		if(bAutoCreateGitAttributes)
		{
			// 2.c. Synchronous (very quick) "lfs install" operation: needs only to be run once by user
			GitSourceControlUtils::RunCommand(TEXT("install"), PathToGitBinary, PathToProjectDir, FGitSourceControlModule::GetEmptyStringArray(), FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);

			// 2.d. Create a ".gitattributes" file to enable Git LFS (Large File System) for the whole "Content/" subdir
			const FString GitAttributesFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT(".gitattributes"));
			FString GitAttributesContent;
			if (GitSourceControl.GetProvider().UsesCheckout())
			{
				// Git LFS 2.x File Locking mechanism
				GitAttributesContent = TEXT("Content/** filter=lfs diff=lfs merge=lfs -text lockable\n");
			}
			else
			{
				GitAttributesContent = TEXT("Content/** filter=lfs diff=lfs merge=lfs -text\n");
			}
			if(FFileHelper::SaveStringToFile(GitAttributesContent, *GitAttributesFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
			{
				ProjectFiles.Add(GitAttributesFilename);
			}
		}

		// 3. Add files to Revision Control: launch an asynchronous MarkForAdd operation
		LaunchMarkForAddOperation(ProjectFiles);

		// 4. The CheckIn will follow, at completion of the MarkForAdd operation
		FGitSourceControlProvider& Provider = FGitSourceControlModule::Get().GetProvider();
		Provider.CheckRepositoryStatus();
	}
	return FReply::Handled();
}

// Launch an asynchronous "MarkForAdd" operation and start an ongoing notification
void SGitSourceControlSettings::LaunchMarkForAddOperation(const TArray<FString>& InFiles)
{
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	TSharedRef<FMarkForAdd, ESPMode::ThreadSafe> MarkForAddOperation = ISourceControlOperation::Create<FMarkForAdd>();
#if ENGINE_MAJOR_VERSION >= 5
	ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(MarkForAddOperation, FSourceControlChangelistPtr(), InFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
#else
	ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(MarkForAddOperation, InFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
#endif
	if (Result == ECommandResult::Succeeded)
	{
		DisplayInProgressNotification(MarkForAddOperation);
	}
	else
	{
		DisplayFailureNotification(MarkForAddOperation);
	}
}

// Launch an asynchronous "CheckIn" operation and start another ongoing notification
void SGitSourceControlSettings::LaunchCheckInOperation()
{
	TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
	CheckInOperation->SetDescription(InitialCommitMessage);
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
#if ENGINE_MAJOR_VERSION >= 5
	ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(CheckInOperation, FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
#else
	ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(CheckInOperation, FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
#endif
	if (Result == ECommandResult::Succeeded)
	{
		DisplayInProgressNotification(CheckInOperation);
	}
	else
	{
		DisplayFailureNotification(CheckInOperation);
	}
}

/// Delegate called when a Revision control operation has completed: launch the next one and manage notifications
void SGitSourceControlSettings::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
	RemoveInProgressNotification();

	// Report result with a notification
	if (InResult == ECommandResult::Succeeded)
	{
		DisplaySuccessNotification(InOperation);
	}
	else
	{
		DisplayFailureNotification(InOperation);
	}

	if ((InOperation->GetName() == "MarkForAdd") && (InResult == ECommandResult::Succeeded) && bAutoInitialCommit)
	{
		// 4. optional initial Asynchronous commit with custom message: launch a "CheckIn" Operation
		LaunchCheckInOperation();
	}
}


// Display an ongoing notification during the whole operation
void SGitSourceControlSettings::DisplayInProgressNotification(const FSourceControlOperationRef& InOperation)
{
	FNotificationInfo Info(InOperation->GetInProgressString());
	Info.bFireAndForget = false;
	Info.ExpireDuration = 0.0f;
	Info.FadeOutDuration = 1.0f;
	OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
	if (OperationInProgressNotification.IsValid())
	{
		OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
	}
}

// Remove the ongoing notification at the end of the operation
void SGitSourceControlSettings::RemoveInProgressNotification()
{
	if (OperationInProgressNotification.IsValid())
	{
		OperationInProgressNotification.Pin()->ExpireAndFadeout();
		OperationInProgressNotification.Reset();
	}
}

// Display a temporary success notification at the end of the operation
void SGitSourceControlSettings::DisplaySuccessNotification(const FSourceControlOperationRef& InOperation)
{
	const FText NotificationText = FText::Format(LOCTEXT("InitialCommit_Success", "{0} operation was successfull!"), FText::FromName(InOperation->GetName()));
	FNotificationInfo Info(NotificationText);
	Info.bUseSuccessFailIcons = true;
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
	Info.Image = FAppStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
#else
	Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
#endif
	FSlateNotificationManager::Get().AddNotification(Info);
}

// Display a temporary failure notification at the end of the operation
void SGitSourceControlSettings::DisplayFailureNotification(const FSourceControlOperationRef& InOperation)
{
	const FText NotificationText = FText::Format(LOCTEXT("InitialCommit_Failure", "Error: {0} operation failed!"), FText::FromName(InOperation->GetName()));
	FNotificationInfo Info(NotificationText);
	Info.ExpireDuration = 8.0f;
	FSlateNotificationManager::Get().AddNotification(Info);
}

void SGitSourceControlSettings::OnCheckedCreateGitIgnore(ECheckBoxState NewCheckedState)
{
	bAutoCreateGitIgnore = (NewCheckedState == ECheckBoxState::Checked);
}

void SGitSourceControlSettings::OnCheckedCreateReadme(ECheckBoxState NewCheckedState)
{
	bAutoCreateReadme = (NewCheckedState == ECheckBoxState::Checked);
}

bool SGitSourceControlSettings::GetAutoCreateReadme() const
{
	return bAutoCreateReadme;
}

void SGitSourceControlSettings::OnReadmeContentCommited(const FText& InText, ETextCommit::Type InCommitType)
{
	ReadmeContent = InText;
}

FText SGitSourceControlSettings::GetReadmeContent() const
{
	return ReadmeContent;
}

void SGitSourceControlSettings::OnCheckedCreateGitAttributes(ECheckBoxState NewCheckedState)
{
	bAutoCreateGitAttributes = (NewCheckedState == ECheckBoxState::Checked);
}

void SGitSourceControlSettings::OnCheckedUseGitLfsLocking(ECheckBoxState NewCheckedState)
{
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	GitSourceControl.AccessSettings().SetUsingGitLfsLocking(NewCheckedState == ECheckBoxState::Checked);
	GitSourceControl.AccessSettings().SaveSettings();
	GitSourceControl.GetProvider().UpdateSettings();
}

bool SGitSourceControlSettings::GetIsUsingGitLfsLocking() const
{
	const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	return GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
}

ECheckBoxState SGitSourceControlSettings::IsUsingGitLfsLocking() const
{
	return (GetIsUsingGitLfsLocking() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked);
}

void SGitSourceControlSettings::OnLfsUserNameCommited(const FText& InText, ETextCommit::Type InCommitType)
{
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	GitSourceControl.AccessSettings().SetLfsUserName(InText.ToString());
	GitSourceControl.AccessSettings().SaveSettings();
	GitSourceControl.GetProvider().UpdateSettings();
}

FText SGitSourceControlSettings::GetLfsUserName() const
{
	FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
	const FString LFSUserName = GitSourceControl.AccessSettings().GetLfsUserName();
	if (LFSUserName.IsEmpty())
	{
		const FText& UserName = GetUserName();
		GitSourceControl.AccessSettings().SetLfsUserName(UserName.ToString());
		GitSourceControl.AccessSettings().SaveSettings();
		GitSourceControl.GetProvider().UpdateSettings();
		return UserName;
	}
	else
	{
		return FText::FromString(LFSUserName);
	}
}

void SGitSourceControlSettings::OnCheckedInitialCommit(ECheckBoxState NewCheckedState)
{
	bAutoInitialCommit = (NewCheckedState == ECheckBoxState::Checked);
}

bool SGitSourceControlSettings::GetAutoInitialCommit() const
{
	return bAutoInitialCommit;
}

void SGitSourceControlSettings::OnInitialCommitMessageCommited(const FText& InText, ETextCommit::Type InCommitType)
{
	InitialCommitMessage = InText;
}

FText SGitSourceControlSettings::GetInitialCommitMessage() const
{
	return InitialCommitMessage;
}

void SGitSourceControlSettings::OnRemoteUrlCommited(const FText& InText, ETextCommit::Type InCommitType)
{
	RemoteUrl = InText;
}

FText SGitSourceControlSettings::GetRemoteUrl() const
{
	return RemoteUrl;
}

#undef LOCTEXT_NAMESPACE