// Project Lab - NHTV IGAD
//////////////////////////////////////////
// Author: Yoshi van Belkom - 130118
//////////////////////////////////////////

#include "MantisPrivatePCH.h"
#include "ISettingsModule.h"
#include "MantisSettings.h"
#include "Runtime/Launch/Resources/Version.h"
#if PLATFORM_WINDOWS
	#include <Winver.h>
	#pragma comment( lib, "version.lib" )
#endif

//defines
#define LOCTEXT_NAMESPACE "Mantis" 
#define STRINGIZE_(x) #x
#define STRINGIZE(x) STRINGIZE_(x)
#define PlatformFile FPlatformFileManager::Get().GetPlatformFile()

class FMantis : public IMantis
{
public:
	/* IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	/* End IModuleInterface implementation */
	FString GetEngineCrashClientPath();
	FString GetMantisCrashClientPath();
	void RegisterSettings();
	void UnregisterSettings();
	int32 IsMantisExe( FString FilePath );
	void UpdateMantis();
	bool MakeBackup( FString Original, FString Backup, bool bOverwrite = false );
};

DEFINE_LOG_CATEGORY(LogMantis);

//Get the path of the Engine Crash Report
FString FMantis::GetEngineCrashClientPath()
{
	//Get Engine root path.
	FString EnginePath = FPaths::RootDir();
	FPaths::NormalizeDirectoryName(EnginePath);
	TArray< FString > EnginePathDirs;
	EnginePath.ParseIntoArray(EnginePathDirs, TEXT("/"), 1);

	//Get the path to the CrashReportClient.exe
	FString EngineCrashClientPath;
	EnginePathDirs.Push(TEXT("Engine"));
	EnginePathDirs.Push(TEXT("Binaries"));
	EnginePathDirs.Push(TEXT("Win64"));
	EnginePathDirs.Push(TEXT("CrashReportClient.exe"));
	EngineCrashClientPath = FString::Join(EnginePathDirs, TEXT("/"));

	//Check if file exists.
	if (FPaths::FileExists(EngineCrashClientPath))
	{
		UE_LOG(LogMantis, Log, TEXT("Engine Crash Report file found!"));
	}
	else
	{
		UE_LOG(LogMantis, Warning, TEXT("Engine Crash Report file not found!"));
	}

	return EngineCrashClientPath;
}

FString FMantis::GetMantisCrashClientPath()
{
	//Get the path of the Project File
	FString ProjectPath = FPaths::GetPath( FPaths::GetProjectFilePath() );
	FPaths::NormalizeDirectoryName( ProjectPath );
	//Get the path to the Mantis Crash Report
	TArray< FString > ProjectPathDirs;
	ProjectPath.ParseIntoArray( ProjectPathDirs, TEXT( "/" ), 1 );
	FString MantisCrashClientPath;
	ProjectPathDirs.Push( TEXT( "Plugins" ) );
	ProjectPathDirs.Push( TEXT( "Mantis" ) );
	ProjectPathDirs.Push( TEXT( "Resources" ) );
	ProjectPathDirs.Push( TEXT( "CrashReporter" ) );
	ProjectPathDirs.Push( TEXT( STRINGIZE( ENGINE_MAJOR_VERSION ) "." STRINGIZE( ENGINE_MINOR_VERSION ) ) );
#if PLATFORM_64BITS
	ProjectPathDirs.Push( TEXT( "Win64" ) );
#else
	ProjectPathDirs.Push( TEXT( "Win32" ) );
#endif
	ProjectPathDirs.Push( TEXT( "CrashReportClient.exe" ) );
	MantisCrashClientPath = FString::Join( ProjectPathDirs, TEXT( "/" ) );
	return MantisCrashClientPath;
}

void FMantis::RegisterSettings()
{
	ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");

	if (SettingsModule != nullptr)
	{
		SettingsModule->RegisterSettings("Project", "Plugins", "MantisPlugin",
			LOCTEXT("MantisSettingsName", "Mantis Plugin"),
			LOCTEXT("MantisSettingsDescription", "Set the setting for the Mantis Plugin."),
			GetMutableDefault<UMantisSettings>()
			);
	}
	else
	{
		UE_LOG(LogMantis, Log, TEXT("SettingsModule is null!"));
	}
}

void FMantis::UnregisterSettings()
{
	ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
	if (SettingsModule != nullptr)
	{
		SettingsModule->UnregisterSettings("Project", "Plugins", "MantisPlugin");
	}
}

void FMantis::UpdateMantis()
{
	//Get the path of the Engine Crash Report
	FString EngineCrashClientPath = GetEngineCrashClientPath();
	FString EngineCrashClientBackupPath = EngineCrashClientPath + ".ue";
	FString EngineCrashClientTmpPath = EngineCrashClientPath + ".tmp";

	//Get the path to the Mantis Crash Report
	FString MantisCrashClientPath = GetMantisCrashClientPath();
	UE_LOG( LogMantis, Log, TEXT( "MantisCrashClientPath: %s" ), *MantisCrashClientPath );
	if ( FPaths::FileExists( MantisCrashClientPath ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "Mantis Crash Report file found!" ) );
	}
	else
	{
		UE_LOG( LogMantis, Warning, TEXT( "Mantis Crash Report file not found!" ) );
		return;
	}

	//Removing the read-only tag
	PlatformFile.SetReadOnly( *EngineCrashClientPath, false );

	if ( IsMantisExe( EngineCrashClientPath ) == 1 )
	{
		FDateTime EngineTimestamp = PlatformFile.GetTimeStamp( *EngineCrashClientPath );
		FDateTime MantisTimestamp = PlatformFile.GetTimeStamp( *MantisCrashClientPath );
		if ( EngineTimestamp >= MantisTimestamp )
		{
			UE_LOG( LogMantis, Log, TEXT( "Crash Report Client is up-to-date!" ) );
			return;
		}
		else
		{
			UE_LOG( LogMantis, Log, TEXT( "Crash Report Client is out-of-date!" ) );
		}
	}

	//Make temporary backup
	if ( !MakeBackup( EngineCrashClientPath, EngineCrashClientTmpPath, true ) )
	{
		UE_LOG( LogMantis, Warning, TEXT( "Couldn't make temporary Crash Report Client backup!" ) );
		return;
	}

	//Delete current file.
	if ( !PlatformFile.DeleteFile( *EngineCrashClientPath ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "Failed to delete the current Crash Report Client." ) );
		return;
	}

	//Copy file if out of date.
	if ( PlatformFile.CopyFile( *EngineCrashClientPath, *MantisCrashClientPath ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "Crash Report Client is updated!" ) );
	}
	else
	{
		int32 errorNum = FPlatformMisc::GetLastError();
		UE_LOG( LogMantis, Warning, TEXT( "Failed to update Crash Report Client! (Error: %i)" ), errorNum );

		if ( PlatformFile.CopyFile( *EngineCrashClientPath, *EngineCrashClientPath ) )
		{
			UE_LOG( LogMantis, Log, TEXT( "Restored Crash Report Client." ) );
		}
		else
		{
			errorNum = FPlatformMisc::GetLastError();
			UE_LOG( LogMantis, Warning, TEXT( "Failed to restore Crash Report Client! (Error: %i)" ), errorNum );
		}
		return;
	}

	//Delete temporary backup file.
	if ( !PlatformFile.DeleteFile( *EngineCrashClientTmpPath ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "Failed to delete the current Crash Report Client." ) );
		return;
	}

	//Removing the read-only tag
	PlatformFile.SetReadOnly( *EngineCrashClientPath, false );
}

bool FMantis::MakeBackup( FString Original, FString Backup, bool bOverwrite )
{
	//Delete backup if it already exists.
	if ( bOverwrite && FPaths::FileExists( Backup ) )
	{
		PlatformFile.DeleteFile( *Backup );
	}

	//Make backup.
	if ( PlatformFile.CopyFile( *Backup, *Original ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "Made a backup." ) );
	}
	else
	{
		int32 errorNum = FPlatformMisc::GetLastError();
		UE_LOG( LogMantis, Warning, TEXT( "Original path: %s" ), *Original );
		UE_LOG( LogMantis, Warning, TEXT( "Backup path: %s" ), *Backup );
		UE_LOG( LogMantis, Warning, TEXT( "Failed to make a backup! (Error: %i)" ), errorNum );
		return false;
	}
	return true;
}

void FMantis::StartupModule()
{
	//Register the settings in the editor.
	RegisterSettings();

#if PLATFORM_WINDOWS
	//Get the path of the Engine Crash Report
	FString EngineCrashClientPath = GetEngineCrashClientPath();
	FString EngineCrashClientBackupPath = EngineCrashClientPath + ".ue";

	int32 Result = IsMantisExe( EngineCrashClientPath );
	if ( Result == 1 )
	{
		UpdateMantis();
	}
	else if ( Result == 0 )
	{
		MakeBackup( EngineCrashClientPath, EngineCrashClientBackupPath, true );
		UpdateMantis();
	}

#endif
}

void FMantis::ShutdownModule()
{	
	//Unregister the settings in the editor.
	UnregisterSettings();

#if PLATFORM_WINDOWS
	//Get the path of the Engine Crash Report
	FString EngineCrashClientPath = GetEngineCrashClientPath();
	FString EngineCrashClientBackupPath = EngineCrashClientPath + ".ue";

	//Can't restore if there is no backup.
	if ( !FPaths::FileExists( EngineCrashClientBackupPath ) )
	{
		UE_LOG( LogMantis, Warning, TEXT( "Backup file not found!" ) );
		return;
	}

	//Check if backup is valid
	if ( IsMantisExe( EngineCrashClientBackupPath ) == 1 )
	{
		UE_LOG( LogMantis, Warning, TEXT( "Backup file is not from Unreal!" ) );
		return;
	}

	//Check if client is valid
	int32 Result = IsMantisExe( EngineCrashClientPath );
	if ( Result == 1 )
	{
		MakeBackup( EngineCrashClientBackupPath, EngineCrashClientPath, true );
	}
#endif
	// This function may be called during shutdown to clean up the module. For modules that support dynamic reloading,
	// this function gets called before unloading the module.
}

int32 FMantis::IsMantisExe( FString FilePath )
{
#if PLATFORM_WINDOWS
	// get the filename of the executable containing the version resource
	LPWSTR FilePathW = (WCHAR*) *FilePath;

	// allocate a block of memory for the version info
	uint32 dummy;
	uint32 dwSize = GetFileVersionInfoSize( FilePathW, (LPDWORD)&dummy );
	if ( dwSize == 0 )
	{
		UE_LOG( LogMantis, Log, TEXT( "GetFileVersionInfoSize failed with error %d\n" ), FPlatformMisc::GetLastError() );
		return -1;
	}

	TArray<BYTE> data;
	data.SetNum( dwSize );
	// load the version info
	if ( !GetFileVersionInfo( FilePathW, NULL, dwSize, &data[0] ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "GetFileVersionInfo failed with error %d\n" ), FPlatformMisc::GetLastError() );
		return -1;
	}

	LPVOID pvFileDescription = NULL;
	uint32 FileDescriptionLen = 0;

	if ( !VerQueryValue( &data[0], _T( "\\StringFileInfo\\040904b0\\FileDescription" ), &pvFileDescription, &FileDescriptionLen ) )
	{
		UE_LOG( LogMantis, Log, TEXT( "Can't obtain ProductName and ProductVersion from resources\n" ) );
		return -1;
	}
	
	FString MantisClientName = TEXT( "MantisCrashReportClient" );
	TCHAR* ClientNameW = (TCHAR*)pvFileDescription;
	FString ClientName = ClientNameW;
	return ( ClientName == MantisClientName ) ? 1 : 0;
#else
	return 0;
#endif
}

IMPLEMENT_MODULE( FMantis, Mantis )

#undef LOCTEXT_NAMESPACE