// Copyright Epic Games, Inc. All Rights Reserved.

#include "CsdkOutputDeviceFile.h"
#include "Misc/AssertionMacros.h"
#include "Templates/UnrealTemplate.h"
#include "Serialization/Archive.h"
#include "Containers/Array.h"
#include "Containers/Set.h"
#include "Containers/UnrealString.h"
#include "Misc/DateTime.h"
#include "HAL/PlatformTime.h"
#include "HAL/FileManager.h"
#include "Misc/OutputDeviceHelper.h"
#include "Misc/Paths.h"
#include "Misc/CommandLine.h"

typedef uint8 UTF8BOMType[3];
static UTF8BOMType UTF8BOM = { 0xEF, 0xBB, 0xBF };


/** [WRITER THREAD] Flushes the archive and reset the flush timer. */

/**

*/
/*
struct FCsdkOutputDeviceFile::FCategoryInclusionInternal
{
	TSet<FName> IncludedCategories;
};
*/

/** 
 * Constructor, initializing member variables.
 *
 * @param InFilename		Filename to use, can be NULL
 * @param bInDisableBackup	If true, existing files will not be backed up
 */
FCsdkOutputDeviceFile::FCsdkOutputDeviceFile( const TCHAR* InFilename, bool bInDisableBackup, bool bInAppendIfExists, bool bCreateWriterLazily)
: AsyncWriter(nullptr)
, WriterArchive(nullptr)
, bAppendIfExists(bInAppendIfExists)
, bDead(false)
//, CategoryInclusionInternal(nullptr)
, bDisableBackup(bInDisableBackup)
{
	if( InFilename )
	{
		FCString::Strncpy( Filename, InFilename, UE_ARRAY_COUNT(Filename) );
	}
	else
	{
		Filename[0]	= 0;
	}
	if (!bCreateWriterLazily) //懒汉式创建writer
	{
		CreateWriter();
	}
}


FCsdkOutputDeviceFile::~FCsdkOutputDeviceFile()
{
	Clear();
}

void FCsdkOutputDeviceFile::TearDown()
{
	Clear();
}


void FCsdkOutputDeviceFile::Flush()
{
	if (AsyncWriter)
	{
		AsyncWriter->Flush();
	}
}


void FCsdkOutputDeviceFile::CreateBackupCopy(const TCHAR* Filename)
{
	IFileManager& FileManager = IFileManager::Get();
	if (FileManager.FileSize(Filename) > 0) // file exists and is not empty
	{
		FString Name, Extension;
		FString(Filename).Split(TEXT("."), &Name, &Extension, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
		FDateTime OriginalTime = FileManager.GetTimeStamp(Filename);
		FString BackupFilename = FString::Printf(TEXT("%s%s%s.%s"), *Name, BACKUP_LOG_FILENAME_POSTFIX, *OriginalTime.ToString(), *Extension);
		//拷贝会失败
		if (FileManager.Copy(*BackupFilename, Filename, false) == COPY_OK)
		{
			FileManager.SetTimeStamp(*BackupFilename, OriginalTime);
		}
		// We use Copy + SetTimeStamp instead of Move because caller might want to append to original log file.
	}
}

bool FCsdkOutputDeviceFile::IsBackupCopy(const TCHAR* Filename)
{
	return Filename != nullptr && FCString::Stristr(const_cast<TCHAR*>(Filename), BACKUP_LOG_FILENAME_POSTFIX) != nullptr;
}

void FCsdkOutputDeviceFile::WriteByteOrderMarkToArchive(EByteOrderMark ByteOrderMark)
{
	switch (ByteOrderMark)
	{
	case EByteOrderMark::UTF8:
		//输出UFT8-BOM格式
		AsyncWriter->Serialize(UTF8BOM, sizeof(UTF8BOM));
		break;

	case EByteOrderMark::Unspecified:
	default:
		check(false);
		break;
	}
}

void FCsdkOutputDeviceFile::Clear()
{
	if (AsyncWriter)
	{
		if (!bSuppressEventTag)
		{			
			Logf(TEXT("Log file closed, %s"), FPlatformTime::StrTimestamp());
		}
		delete AsyncWriter;
		AsyncWriter = nullptr;
	}
	delete WriterArchive;
	WriterArchive = nullptr;

	Filename[0] = 0;
}

bool FCsdkOutputDeviceFile::HasWriter() const
{
	return AsyncWriter != nullptr;
}

bool FCsdkOutputDeviceFile::CreateWriter(uint32 MaxAttempts)
{
	if (HasWriter())
	{
		return true;
	}
#if CSDK_WITH_CHECK
	checkf(Filename[0],TEXT("cpp file name %s the %d line no Filename "),TEXT(__FILE__),__LINE__);
#endif

	//如果文件已存在，就创建文件的备份
	if (!bDisableBackup)
	{
		CreateBackupCopy(Filename);
	}

	// Create a silent filewriter so that it doesn't try to log any errors since it would redirect logging back to itself through this output device
	uint32 WriteFlags = FILEWRITE_Silent | FILEWRITE_AllowRead | (bAppendIfExists ? FILEWRITE_Append : 0);

	// Open log file.
	FArchive* Ar = IFileManager::Get().CreateFileWriter(Filename, WriteFlags);

	// 创建备份失败，就按照_n模式添加
	if (!bDisableBackup && !Ar)
	{
		FString FilenamePart = FPaths::GetBaseFilename(Filename, false) + "_";
		FString ExtensionPart = FPaths::GetExtension(Filename, true);
		FString FinalFilename;
		uint32 FileIndex = 2;
		do
		{
			// Continue to increment indices until a valid filename is found
			FinalFilename = FilenamePart + FString::FromInt(FileIndex++) + ExtensionPart;
			//给_n文件创建备份
			CreateBackupCopy(*FinalFilename);
			//将文件赋予备份的文件名
			FCString::Strcpy(Filename, UE_ARRAY_COUNT(Filename), *FinalFilename);
			Ar = IFileManager::Get().CreateFileWriter(*FinalFilename, WriteFlags);
		} while (!Ar && FileIndex < MaxAttempts);
	}

	if (Ar)
	{
		WriterArchive = Ar;
		AsyncWriter = new FHeroAsyncWriter(*WriterArchive);
		WriteByteOrderMarkToArchive(EByteOrderMark::UTF8);

		if (!bSuppressEventTag)
		{
			Logf(TEXT("Log file open, %s"), FPlatformTime::StrTimestamp());
		}
		return true;
	}
	else
	{
		return false;
	}
}

/**
 * Serializes the passed in data unless the current event is suppressed.
 *
 * @param	Data	Text to log
 * @param	Event	Event name used for suppression purposes
 */
void FCsdkOutputDeviceFile::Serialize( const TCHAR* Data, ELogVerbosity::Type Verbosity, const class FName& Category, const double Time )
{
	//checkf(bDead,TEXT("create writer failed for some reason"));
	/*if (CategoryInclusionInternal && !CategoryInclusionInternal->IncludedCategories.Contains(Category))
	{
		return;
	}*/

	static bool Entry = false;
	if( !GIsCriticalError || Entry )
	{
		if (!AsyncWriter)
		{
			// 懒汉式创建
			if (!CreateWriter())
			{
				bDead = true;
			}
		}

		if (AsyncWriter && Verbosity != ELogVerbosity::SetColor)
		{
			FOutputDeviceHelper::FormatCastAndSerializeLine(*AsyncWriter, Data, Verbosity, Category, Time, bSuppressEventTag, bAutoEmitLineTerminator);

			static bool GForceLogFlush = false;
			static bool GTestedCmdLine = false;
			if (!GTestedCmdLine)
			{
				GTestedCmdLine = true;
				//从命令行配置，强制刷新
				GForceLogFlush = FParse::Param( FCommandLine::Get(), TEXT("FORCE_CSDK_LOG_FLUSH") );
			}
			if (GForceLogFlush)
			{
				AsyncWriter->Flush();
			}
		}
	}
	else//致命错误
	{
		Entry = true;
		Serialize(Data, Verbosity, Category, Time);
		Entry = false;
	}
}

void FCsdkOutputDeviceFile::Serialize( const TCHAR* Data, ELogVerbosity::Type Verbosity, const class FName& Category )
{
	Serialize(Data, Verbosity, Category, -1.0);
}



/*
void FCsdkOutputDeviceFile::IncludeCategory(const FName& InCategoryName)
{
	if (!CategoryInclusionInternal.IsValid())
	{
		CategoryInclusionInternal = MakeUnique<FCategoryInclusionInternal>();
	}

	CategoryInclusionInternal->IncludedCategories.Add(InCategoryName);
}*/
