#pragma warning(push)
#pragma warning(disable:4141)
#pragma warning(disable:4146)
#pragma warning(disable:4291)
#pragma warning(disable:4244)
#pragma warning(disable:4267)
#pragma warning(disable:4624)
#pragma warning(disable:4800)
#pragma warning(disable:4996)

#include "BfCompiler.h"
#include "BfCodeGen.h"
#include "BfIRCodeGen.h"
#include "BfModule.h"
#include "BeefySysLib/FileStream.h"
#include "BeefySysLib/util/PerfTimer.h"
#include "BeefySysLib/util/BeefPerf.h"

#ifdef BF_PLATFORM_WINDOWS
#include "../Backend/BeIRCodeGen.h"
#include "../Backend/BeCOFFObject.h"
#include "../Backend/BeLibManger.h"
#endif

//#define BF_IGNORE_BACKEND_HASH

//#include "../X86.h"

#ifdef _DEBUG
//#define MAX_THREADS 3
//#define MAX_THREADS 1
//#define MAX_THREADS 6
//#define BF_USE_CODEGEN_RELEASE_THUNK
#else
//#define MAX_THREADS 6
//#define MAX_THREADS 8
//#define MAX_THREADS 1
#endif

//#define CODEGEN_DISABLE_CACHE
//#define CODEGEN_DISABLE_CACHE_BEEFBACKEND

// Emergency debugging only! Slow!
//#define DBG_FORCE_SYNCHRONIZED

// This is used for the Release DLL thunk and the build.dat file
#define BF_CODEGEN_VERSION 14

#undef DEBUG

#pragma warning(disable:4267)

#include "BeefySysLib/util/AllocDebug.h"

#pragma warning(pop)

USING_NS_BF;
using namespace llvm;

String BfCodeGenDirectoryData::GetDataFileName()
{
	return mDirectoryName + "/build.dat";
}

void BfCodeGenDirectoryData::Read()
{
	FileStream fileStream;
	String fileName = GetDataFileName();
	if (!fileStream.Open(fileName, "rb"))
		return;

	int fileId = fileStream.ReadInt32();
	if (fileId != 0xBEEF0100)
		return;

	int version = fileStream.ReadInt32();
	if (version != BF_CODEGEN_VERSION)
		return;

	Val128 backendHash;
	fileStream.ReadT(backendHash);
#ifndef BF_IGNORE_BACKEND_HASH
	if (mCodeGen->mBackendHash != backendHash)
		return;
#endif

	int numFiles = fileStream.ReadInt32();
	for (int fileIdx = 0; fileIdx < numFiles; fileIdx++)
	{
		String fileName = fileStream.ReadAscii32SizedString();
		BfCodeGenFileData fileData;
		fileStream.Read(&fileData.mIRHash, sizeof(Val128));
		fileStream.Read(&fileData.mIROrderedHash, sizeof(Val128));
		fileStream.Read(&fileData.mLastWasObjectWrite, sizeof(bool));
		mFileMap[fileName] = fileData;
	}

	int numValues = fileStream.ReadInt32();
	for (int valIdx = 0; valIdx < numValues; valIdx++)
	{
		String key = fileStream.ReadAscii32SizedString();
		String value = fileStream.ReadAscii32SizedString();
		mBuildSettings[key] = value;
	}

	mFileTime = GetFileTimeWrite(fileName);
}

void BfCodeGenDirectoryData::Write()
{
	if (mFileFailed)
	{
		// We could be smarter about write failures, but we just nuke eveything here
		//  to ensure that the next rebuild attempts to generate the files again
		mFileMap.Clear();
		mFileFailed = false;
	}

	FileStream fileStream;
	String fileName = GetDataFileName();
	if (!fileStream.Open(fileName, "wb"))
	{
		String directory = GetFileDir(fileName);
		if (!DirectoryExists(directory))
		{
			// Someone else (or the user) cleared this directory
			BfLogX(2, "BfCodeGen cleared cache because '%s' was removed\n", directory.c_str());
			mFileMap.Clear();
			return;
		}

		mError = "Failed to write to " + fileName;
		return;
	}

	fileStream.Write((int)0xBEEF0100);
	fileStream.Write(BF_CODEGEN_VERSION);
	fileStream.WriteT(mCodeGen->mBackendHash);

	fileStream.Write((int)mFileMap.size());
	for (auto& pair : mFileMap)
	{
		fileStream.Write(pair.mKey);
		fileStream.Write(&pair.mValue.mIRHash, sizeof(Val128));
		fileStream.Write(&pair.mValue.mIROrderedHash, sizeof(Val128));
		fileStream.Write(&pair.mValue.mLastWasObjectWrite, sizeof(bool));
	}

	fileStream.Write((int)mBuildSettings.size());
	for (auto& pair : mBuildSettings)
	{
		fileStream.Write(pair.mKey);
		fileStream.Write(pair.mValue);
	}

	fileStream.Close();
	mFileTime = GetFileTimeWrite(fileName);
}

void BfCodeGenDirectoryData::Verify()
{
	if (!mError.empty())
		return;

	String fileName = GetDataFileName();
	int64 fileTimeWrite = GetFileTimeWrite(fileName);
	if ((fileTimeWrite == mFileTime) || (mFileTime == 0))
	{
		mVerified = true;
	}
	else
	{
		mError = "Build directory corrupted, perform clean";
	}
}

void BfCodeGenDirectoryData::Clear()
{
	mFileMap.Clear();
	mBuildSettings.Clear();
}

bool BfCodeGenDirectoryData::CheckCache(const StringImpl& fileName, Val128 hash, Val128* outOrderedHash, bool disallowObjectWrite)
{
	if (!mVerified)
		Verify();

	BfCodeGenFileData* fileData = NULL;

	if (!mFileMap.TryAdd(fileName, NULL, &fileData))
	{
		if ((fileData->mLastWasObjectWrite) && (disallowObjectWrite))
			return false;
		if (outOrderedHash != NULL)
			*outOrderedHash = fileData->mIROrderedHash;
		if (fileData->mIRHash == hash)
			return true;
		fileData->mIRHash = hash;
		return false;
	}

	fileData->mLastWasObjectWrite = false;
	fileData->mIRHash = hash;
	return false;
}

void BfCodeGenDirectoryData::SetHash(const StringImpl& fileName, Val128 hash, Val128 orderedHash, bool isObjectWrite)
{
	if (!mVerified)
		Verify();

	BfCodeGenFileData* fileData = NULL;

	mFileMap.TryAdd(fileName, NULL, &fileData);
	fileData->mIRHash = hash;
	fileData->mIROrderedHash = orderedHash;
	fileData->mLastWasObjectWrite = isObjectWrite;
}

void BfCodeGenDirectoryData::ClearHash(const StringImpl& fileName)
{
	mFileMap.Remove(fileName);
}

void BfCodeGenDirectoryData::FileFailed()
{
	mFileFailed = true;
}

String BfCodeGenDirectoryData::GetValue(const StringImpl& key)
{
	String* valuePtr = NULL;
	if (mBuildSettings.TryGetValue(key, &valuePtr))
	{
		return *valuePtr;
	}
	return String();
}

void BfCodeGenDirectoryData::SetValue(const StringImpl& key, const StringImpl& value)
{
	mBuildSettings[key] = value;
}

//////////////////////////////////////////////////////////////////////////

void BfCodeGenRequest::DbgSaveData()
{
	/*FILE* fp = fopen("c:\\temp\\dbgOut.bc", "wb");
	if (fp == NULL)
	{
		Beefy::OutputDebugStrF("Failed to write\n");
	}
	fwrite(mData.begin(), mData.size(), 1, fp);
	fclose(fp);*/
}

void DbgSaveData(BfCodeGenRequest* genRequest)
{
	genRequest->DbgSaveData();
}

//////////////////////////////////////////////////////////////////////////

BfCodeGenThread::BfCodeGenThread()
{
	mShuttingDown = false;
	mRunning = false;
	mThreadIdx = 0;
	mCodeGen = NULL;
}

BfCodeGenThread::~BfCodeGenThread()
{
	Shutdown();
}

void BfCodeGenThread::RunLoop()
{
	String threadName = StrFormat("CodeGen/Worker %d", mThreadIdx);
	BpSetThreadName(threadName.c_str());
	BfpThread_SetName(NULL, threadName.c_str(), NULL);

	while (!mShuttingDown)
	{
		BfCodeGenRequest* request = NULL;
		{
			AutoCrit autoCrit(mCodeGen->mPendingRequestCritSect);
			if (!mCodeGen->mPendingRequests.IsEmpty())
			{
				request = mCodeGen->mPendingRequests[0];
				mCodeGen->mPendingRequests.RemoveAt(0);
			}
		}

		if (request == NULL)
		{
			mCodeGen->mRequestEvent.WaitFor(20);
			continue;
		}

		auto cacheDir = GetFileDir(request->mOutFileName);
		auto cacheFileName = GetFileName(request->mOutFileName);

		StringT<256> objFileName = request->mOutFileName + BF_OBJ_EXT;

		bool hasCacheMatch = false;
		BfCodeGenDirectoryData* dirCache = NULL;

		Val128 hash;
		Val128 orderedHash;

		BfLogX(2, "BfCodeGen handle %s\n", request->mOutFileName.c_str());

		bool isLibWrite = (request->mOptions.mOptLevel == BfOptLevel_OgPlus) && (request->mOptions.mWriteToLib) && (!request->mOptions.mIsHotCompile);

#ifndef CODEGEN_DISABLE_CACHE
		{
			AutoCrit autoCrit(mCodeGen->mCacheCritSect);
			dirCache = mCodeGen->GetDirCache(cacheDir);

			//For testing only!
			/*{
				FileStream fileStr;
				fileStr.Open(request->mOutFileName + ".bfir", "wb");
				fileStr.Write(request->mData.mVals, request->mData.mSize);
			}*/

			HashContext hashCtx;
			hashCtx.Mixin(request->mOptions.mHash);
			hashCtx.Mixin(request->mData.mVals, request->mData.mSize);
			hash = hashCtx.Finish128();

			hasCacheMatch = dirCache->CheckCache(cacheFileName, hash, &orderedHash, isLibWrite);

#ifdef BF_PLATFORM_WINDOWS
			if ((!hash.IsZero()) && (request->mOptions.mWriteToLib) && (request->mOptions.mOptLevel == BfOptLevel_OgPlus))
			{
				if (!BeLibManager::Get()->AddUsedFileName(objFileName))
				{
					// Oops, this library doesn't actually have this file so we can't use it from the cache. This can happen if we use a type,
					//  delete it, and then add it back (for example). The file info will still be in the cache but the lib won't have it.
					orderedHash = Val128();
					hash = Val128();
					hasCacheMatch = false;
				}
			}

#endif
		}
#endif

		//TODO: Temporary, to make sure it keeps getting rebuilt

#ifdef CODEGEN_DISABLE_CACHE_BEEFBACKEND
		if (request->mOptions.mOptLevel == BfOptLevel_OgPlus)
			hasCacheMatch = false;
#endif

		String errorMsg;

		bool doBEProcessing = true; // TODO: Normally 'true' so we do ordered cache check for LLVM too
		if (request->mOptions.mOptLevel == BfOptLevel_OgPlus)
			doBEProcessing = true; // Must do it for this

		BfCodeGenResult result;
		result.mType = BfCodeGenResult_Done;
		result.mErrorMsgBufLen = 0;
		if (hasCacheMatch)
		{
			result.mType = BfCodeGenResult_DoneCached;
		}
		else
		{
#ifdef BF_PLATFORM_WINDOWS
			BeIRCodeGen* beIRCodeGen = new BeIRCodeGen();
			defer ( delete beIRCodeGen; );

			beIRCodeGen->SetConfigConst(BfIRConfigConst_VirtualMethodOfs, request->mOptions.mVirtualMethodOfs);
			beIRCodeGen->SetConfigConst(BfIRConfigConst_DynSlotOfs, request->mOptions.mDynSlotOfs);

			if (doBEProcessing)
			{
				BP_ZONE("ProcessBfIRData");
				beIRCodeGen->Init(request->mData);

				BeHashContext hashCtx;
				hashCtx.Mixin(request->mOptions.mHash);
				beIRCodeGen->Hash(hashCtx);
				auto newOrderedHash = hashCtx.Finish128();
				BfLogX(2, "Ordered hash for %s New:%s Old:%s\n", cacheFileName.c_str(), newOrderedHash.ToString().c_str(), orderedHash.ToString().c_str());
				hasCacheMatch = newOrderedHash == orderedHash;

				errorMsg = beIRCodeGen->mErrorMsg;
				orderedHash = newOrderedHash;
			}

			if (hasCacheMatch)
			{
				result.mType = BfCodeGenResult_DoneCached;
			}

#ifndef CODEGEN_DISABLE_CACHE
			{
				AutoCrit autoCrit(mCodeGen->mCacheCritSect);
				dirCache->SetHash(cacheFileName, hash, orderedHash, !isLibWrite);
			}
#endif

#endif
			if ((hasCacheMatch) || (!errorMsg.IsEmpty()))
			{
				//
			}
			else if (request->mOptions.mOptLevel == BfOptLevel_OgPlus)
			{
#ifdef BF_PLATFORM_WINDOWS
				BP_ZONE("BfCodeGen::RunLoop.Beef");

				if (request->mOptions.mWriteLLVMIR)
				{
					BP_ZONE("BfCodeGen::RunLoop.Beef.IR");
					std::error_code ec;
					String fileName = request->mOutFileName + ".beir";

					String str = beIRCodeGen->mBeModule->ToString();
					FileStream fs;
					if (!fs.Open(fileName, "w"))
					{
						if (!errorMsg.empty())
							errorMsg += "\n";
						errorMsg += "Failed writing IR '" + fileName + "': " + ec.message();
					}
					else
						fs.WriteSNZ(str);
				}

				if (!hasCacheMatch)
					beIRCodeGen->Process();
				errorMsg = beIRCodeGen->mErrorMsg;

				BfLogX(2, "Generating obj %s\n", request->mOutFileName.c_str());

				BeCOFFObject coffObject;
				coffObject.mWriteToLib = request->mOptions.mWriteToLib;
				if (!coffObject.Generate(beIRCodeGen->mBeModule, objFileName))
					errorMsg = StrFormat("Failed to write object file: %s", objFileName.c_str());

				if (!beIRCodeGen->mErrorMsg.IsEmpty())
				{
					if (!errorMsg.IsEmpty())
						errorMsg += "\n";
					errorMsg += beIRCodeGen->mErrorMsg;
				}

#else
				errorMsg = "Failed to generate object file";
#endif
			}
			else
			{
				BP_ZONE_F("BfCodeGen::RunLoop.LLVM %s", request->mOutFileName.c_str());

				BfIRCodeGen* llvmIRCodeGen = new BfIRCodeGen();
				llvmIRCodeGen->SetCodeGenOptions(request->mOptions);
				llvmIRCodeGen->SetConfigConst(BfIRConfigConst_VirtualMethodOfs, request->mOptions.mVirtualMethodOfs);
				llvmIRCodeGen->SetConfigConst(BfIRConfigConst_DynSlotOfs, request->mOptions.mDynSlotOfs);
				llvmIRCodeGen->ProcessBfIRData(request->mData);

				errorMsg = llvmIRCodeGen->mErrorMsg;
				llvmIRCodeGen->mErrorMsg.Clear();

				if (errorMsg.IsEmpty())
				{
					if (request->mOptions.mWriteLLVMIR)
					{
						BP_ZONE("BfCodeGen::RunLoop.LLVM.IR");
						String fileName = request->mOutFileName + ".ll";
						String irError;
						if (!llvmIRCodeGen->WriteIR(fileName, irError))
						{
							if (!errorMsg.empty())
								errorMsg += "\n";
							errorMsg += "Failed writing IR '" + fileName + "': " + irError;
							dirCache->FileFailed();
						}
					}

					if (request->mOptions.mWriteObj)
					{
						BP_ZONE("BfCodeGen::RunLoop.LLVM.OBJ");

						String outFileName;
						if (request->mOptions.mAsmKind != BfAsmKind_None)
							outFileName = request->mOutFileName + ".s";
						else
							outFileName = request->mOutFileName + BF_OBJ_EXT;
						if (!llvmIRCodeGen->WriteObjectFile(outFileName))
						{
							result.mType = BfCodeGenResult_Failed;
							dirCache->FileFailed();
						}
					}
				}

				if (!llvmIRCodeGen->mErrorMsg.IsEmpty())
				{
					if (!errorMsg.IsEmpty())
						errorMsg += "\n";
					errorMsg += llvmIRCodeGen->mErrorMsg;
				}

				delete llvmIRCodeGen;
			}
		}

		if (!errorMsg.IsEmpty())
		{
			result.mType = BfCodeGenResult_Failed;
			int errorStrLen = (int)errorMsg.length();
			if ((result.mErrorMsgBufLen + errorStrLen + 1) < BfCodeGenResult::kErrorMsgBufSize)
			{
				memcpy(&result.mErrorMsgBuf[result.mErrorMsgBufLen], errorMsg.c_str(), errorStrLen + 1);
				result.mErrorMsgBufLen += errorStrLen + 1;
			}

			if (dirCache != NULL)
			{
				AutoCrit autoCrit(mCodeGen->mCacheCritSect);
				dirCache->ClearHash(cacheFileName);
			}
		}

		{
			// It's an extern request, so we own this
			bool deleteRequest = false;
			if (request->mExternResultPtr != NULL)
			{
				*request->mExternResultPtr = result;
				deleteRequest = true;
			}

			// We need this fence for BF_USE_CODEGEN_RELEASE_THUNK usage- because we can't access the request anymore after setting
			//  request->mIsDone
			BF_FULL_MEMORY_FENCE();

			AutoCrit autoCrit(mCodeGen->mPendingRequestCritSect);
			request->mResult = result;
			mCodeGen->mDoneEvent.Set();
			if (deleteRequest)
				delete request;
		}
	}

	mRunning = false;
	mCodeGen->mDoneEvent.Set();
}

void BfCodeGenThread::Shutdown()
{
	mShuttingDown = true;
	if (mRunning)
		mCodeGen->mRequestEvent.Set(true);

	while (mRunning)
	{
		mCodeGen->mDoneEvent.WaitFor(20);
	}
}

static void BFP_CALLTYPE RunLoopThunk(void* codeGenThreadP)
{
	auto codeGenThread = (BfCodeGenThread*)codeGenThreadP;
	BfpThread_SetName(NULL, StrFormat("BfCodeGenThread%d", codeGenThread->mThreadIdx).c_str(), NULL);
	codeGenThread->RunLoop();
}

void BfCodeGenThread::Start()
{
	mRunning = true;
	//TODO: How much mem do we need? WTF- we have 32MB set before!
	auto mThread = BfpThread_Create(RunLoopThunk, (void*)this, 1024 * 1024, BfpThreadCreateFlag_StackSizeReserve);
	BfpThread_SetPriority(mThread, BfpThreadPriority_Low, NULL);
	BfpThread_Release(mThread);
}

//////////////////////////////////////////////////////////////////////////

BfCodeGen::BfCodeGen()
{
	mAttemptedReleaseThunkLoad = false;
	mIsUsingReleaseThunk = false;
	mReleaseModule = NULL;
	mClearCacheFunc = NULL;
	mGetVersionFunc = NULL;
	mKillFunc = NULL;
	mCancelFunc = NULL;
	mFinishFunc = NULL;
	mGenerateObjFunc = NULL;

	mRequestIdx = 0;
#ifdef MAX_THREADS
	mMaxThreadCount = MAX_THREADS;
#else
	mMaxThreadCount = 6;
#endif
	mQueuedCount = 0;
	mCompletionCount = 0;
	mDisableCacheReads = false;

	HashContext hashCtx;
	hashCtx.Mixin(BF_CODEGEN_VERSION);

#ifdef BF_PLATFORM_WINDOWS
	char path[MAX_PATH];
	::GetModuleFileNameA(NULL, path, MAX_PATH);

	if (_strnicmp(path + 2, "\\Beef\\", 6) == 0)
	{
		path[8] = 0;

		char* checkFileNames[] =
		{
			"IDEHelper/Backend/BeCOFFObject.cpp",
			"IDEHelper/Backend/BeCOFFObject.h",
			"IDEHelper/Backend/BeContext.cpp",
			"IDEHelper/Backend/BeContext.h",
			"IDEHelper/Backend/BeDbgModule.h",
			"IDEHelper/Backend/BeIRCodeGen.cpp",
			"IDEHelper/Backend/BeIRCodeGen.h",
			"IDEHelper/Backend/BeLibManager.cpp",
			"IDEHelper/Backend/BeLibManager.h",
			"IDEHelper/Backend/BeMCContext.cpp",
			"IDEHelper/Backend/BeMCContext.h",
			"IDEHelper/Backend/BeModule.cpp",
			"IDEHelper/Backend/BeModule.h",
			"IDEHelper/Compiler/BfCodeGen.cpp",
			"IDEHelper/Compiler/BfCodeGen.h",
			"IDEHelper/Compiler/BfIRBuilder.cpp",
			"IDEHelper/Compiler/BfIRBuilder.h",
			"IDEHelper/Compiler/BfIRCodeGen.cpp",
			"IDEHelper/Compiler/BfIRCodeGen.h",
		};

		for (int i = 0; i < BF_ARRAY_COUNT(checkFileNames); i++)
		{
			String filePath;
			filePath += path;
			filePath += checkFileNames[i];
			auto writeTime = GetFileTimeWrite(filePath);
			hashCtx.Mixin(writeTime);
		}
	}
#endif

	mBackendHash = hashCtx.Finish128();
}

BfCodeGen::~BfCodeGen()
{
	if (mIsUsingReleaseThunk)
		mKillFunc();

	if (mReleaseModule != NULL)
        BfpDynLib_Release(mReleaseModule);

	ClearOldThreads(true);

	for (auto thread : mThreads)
	{
		thread->mShuttingDown = true;
	}
	mRequestEvent.Set(true);

	for (auto thread : mThreads)
	{
		thread->Shutdown();
		delete thread;
	}

	for (auto request : mRequests)
	{
		delete request;
	}

	for (auto& entry : mDirectoryCache)
		delete entry.mValue;
}

void BfCodeGen::ResetStats()
{
	mQueuedCount = 0;
	mCompletionCount = 0;
}

void BfCodeGen::UpdateStats()
{
	while (mRequests.size() != 0)
	{
		auto request = mRequests[0];
		if (request->mResult.mType == BfCodeGenResult_NotDone)
			return;

		RequestComplete(request);
		delete request;
		mRequests.RemoveAt(0);
		return;
	}
}

void BfCodeGen::ClearOldThreads(bool waitForThread)
{
	while (mOldThreads.size() != 0)
	{
		auto thread = mOldThreads[0];
		if (waitForThread)
			thread->Shutdown();
		if (thread->mRunning)
			return;

		delete thread;
		mOldThreads.RemoveAt(0);
	}
}

void BfCodeGen::ClearBuildCache()
{
	AutoCrit autoCrit(mCacheCritSect);
	for (auto& dirCachePair : mDirectoryCache)
	{
		auto dirData = dirCachePair.mValue;
		dirData->Clear();
	}
	// This just disables reading the cache file, but it does not disable creating
	//  the cache structes in memory and writing them out, thus it's valid to leave
	//  this 'true' forever
	mDisableCacheReads = true;

#ifdef BF_USE_CODEGEN_RELEASE_THUNK
	BindReleaseThunks();
	if (mClearCacheFunc != NULL)
		mClearCacheFunc();
#endif
}

void BfCodeGen::DoWriteObjectFile(BfCodeGenRequest* codeGenRequest, const void* ptr, int size, const StringImpl& outFileName, BfCodeGenResult* externResultPtr)
{
	codeGenRequest->mData.mVals = (uint8*)ptr;
	codeGenRequest->mData.mSize = size;
	codeGenRequest->mOutFileName = outFileName;
	codeGenRequest->mResult.mType = BfCodeGenResult_NotDone;
	codeGenRequest->mResult.mErrorMsgBufLen = 0;
	codeGenRequest->mExternResultPtr = externResultPtr;

	int threadIdx = mRequestIdx % mMaxThreadCount;
	if (threadIdx >= (int) mThreads.size())
	{
		AutoCrit autoCrit(mThreadsCritSect);
		BfCodeGenThread* thread = new BfCodeGenThread();
		thread->mThreadIdx = threadIdx;
		thread->mCodeGen = this;
		mThreads.push_back(thread);
		thread->Start();
	}

	auto thread = mThreads[threadIdx];

	BP_ZONE("WriteObjectFile_CritSect");
	AutoCrit autoCrit(mPendingRequestCritSect);
	mPendingRequests.push_back(codeGenRequest);

#ifdef BF_PLATFORM_WINDOWS
	BF_ASSERT(!mRequestEvent.mEvent->mManualReset); // Make sure it's out of the SignalAll state
#endif
	mRequestEvent.Set();

	mRequestIdx++;
}

void BfCodeGen::SetMaxThreads(int maxThreads)
{
#ifndef MAX_THREADS
	mMaxThreadCount = BF_CLAMP(maxThreads, 2, 64);
#endif
}

void BfCodeGen::BindReleaseThunks()
{
	if (mAttemptedReleaseThunkLoad)
		return;

	mAttemptedReleaseThunkLoad = true;
	if (mReleaseModule == NULL)
	{
#ifdef BF32
        mReleaseModule = BfpDynLib_Load("./IDEHelper32.dll");
#else
        mReleaseModule = BfpDynLib_Load("./IDEHelper64.dll");
#endif
		if (mReleaseModule == NULL)
		{
			BF_FATAL("Unable to locate release DLL. Please rebuild Release configuration.\n");
			return;
		}

#ifdef BF32
        mClearCacheFunc = (GetVersionFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "_BfCodeGen_ClearCache@0");
        mGetVersionFunc = (GetVersionFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "_BfCodeGen_GetVersion@0");
        mKillFunc = (KillFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "_BfCodeGen_Kill@0");
        mCancelFunc = (KillFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "_BfCodeGen_Cancel@0");
        mFinishFunc = (FinishFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "_BfCodeGen_Finish@0");
        mGenerateObjFunc = (GenerateObjFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "_BfCodeGen_GenerateObj@20");
#else
        mClearCacheFunc = (GetVersionFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "BfCodeGen_ClearCache");
        mGetVersionFunc = (GetVersionFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "BfCodeGen_GetVersion");
        mKillFunc = (KillFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "BfCodeGen_Kill");
        mCancelFunc = (KillFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "BfCodeGen_Cancel");
        mFinishFunc = (FinishFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "BfCodeGen_Finish");
        mGenerateObjFunc = (GenerateObjFunc)::BfpDynLib_GetProcAddress(mReleaseModule, "BfCodeGen_GenerateObj");
#endif

		if ((mGetVersionFunc == NULL) || (mKillFunc == NULL) || (mCancelFunc == NULL) || (mFinishFunc == NULL) || (mGenerateObjFunc == NULL))
		{
			BF_FATAL("Invalid signature in IDEHelper release DLL. Please rebuild Release configuration.\n");
			return;
		}

		if (mGetVersionFunc() != BF_CODEGEN_VERSION)
		{
			BF_FATAL("Invalid BF_CODEGEN_VERSION in IDEHelper release DLL. Please rebuild Release configuration.\n");
			return;
		}

		mIsUsingReleaseThunk = true;
	}
}

bool BfCodeGen::ExternWriteObjectFile(BfCodeGenRequest* codeGenRequest)
{
#ifndef BF_USE_CODEGEN_RELEASE_THUNK
	return false;
#endif

	BindReleaseThunks();

	if (!mIsUsingReleaseThunk)
		return false;

	mGenerateObjFunc(codeGenRequest->mData.mVals, codeGenRequest->mData.mSize, codeGenRequest->mOutFileName.c_str(), &codeGenRequest->mResult, codeGenRequest->mOptions);

	return true;
}

void BfCodeGen::WriteObjectFile(BfModule* bfModule, const StringImpl& outFileName, const BfCodeGenOptions& options)
{
	mQueuedCount++;

	BfLogSys(bfModule->mSystem, "WriteObjectFile %s\n", outFileName.c_str());

	BfCodeGenRequest* codeGenRequest = new BfCodeGenRequest();
	mRequests.push_back(codeGenRequest);

	{
		BP_ZONE("WriteObjectFile_GetBufferData");
		bfModule->mBfIRBuilder->GetBufferData(codeGenRequest->mOutBuffer);
	}

	auto rootModule = bfModule;
	while (rootModule->mParentModule != NULL)
		rootModule = rootModule->mParentModule;

	codeGenRequest->mSrcModule = rootModule;
	codeGenRequest->mOutFileName = outFileName;
	codeGenRequest->mData = codeGenRequest->mOutBuffer;
	codeGenRequest->mOptions = options;
	if (ExternWriteObjectFile(codeGenRequest))
		return;

	DoWriteObjectFile(codeGenRequest, (void*)&codeGenRequest->mOutBuffer[0], (int)codeGenRequest->mOutBuffer.size(), codeGenRequest->mOutFileName, NULL);

#ifdef DBG_FORCE_SYNCHRONIZED
	while (mRequests.size() != 0)
	{
		UpdateStats();
	}

	BfLogSys(bfModule->mSystem, "WriteObjectFile Done\n");
#endif
}

String BfCodeGen::GetBuildValue(const StringImpl& buildDir, const StringImpl& key)
{
	AutoCrit autoCrit(mCacheCritSect);
	BfCodeGenDirectoryData* dirCache = GetDirCache(buildDir);
	return dirCache->GetValue(key);
}

void BfCodeGen::SetBuildValue(const StringImpl& buildDir, const StringImpl & key, const StringImpl & value)
{
	AutoCrit autoCrit(mCacheCritSect);
	BfCodeGenDirectoryData* dirCache = GetDirCache(buildDir);
	dirCache->SetValue(key, value);
}

void BfCodeGen::WriteBuildCache(const StringImpl& buildDir)
{
	AutoCrit autoCrit(mCacheCritSect);
	BfCodeGenDirectoryData* dirCache = GetDirCache(buildDir);
	dirCache->Write();
}

void BfCodeGen::RequestComplete(BfCodeGenRequest* request)
{
	mCompletionCount++;
	if ((request->mResult.mType == BfCodeGenResult_Failed) || (request->mResult.mType == BfCodeGenResult_Aborted))
	{
		BfCodeGenErrorEntry errorEntry;
		errorEntry.mSrcModule = request->mSrcModule;
		errorEntry.mOutFileName = request->mOutFileName;

		int errorPos = 0;
		while (errorPos < request->mResult.mErrorMsgBufLen)
		{
			char* errorStr = &request->mResult.mErrorMsgBuf[errorPos];
			errorEntry.mErrorMessages.push_back(errorStr);
			errorPos += (int)strlen(errorStr) + 1;
		}

		mFailedRequests.push_back(errorEntry);
	}
	else
	{
		BfCodeGenFileEntry entry;
		entry.mFileName = request->mOutFileName;
		entry.mModule = request->mSrcModule;
		entry.mProject = request->mSrcModule->mProject;
		entry.mWasCached = request->mResult.mType == BfCodeGenResult_DoneCached;
		entry.mModuleHotReferenced = false;
		mCodeGenFiles.push_back(entry);
	}
}

void BfCodeGen::ProcessErrors(BfPassInstance* passInstance, bool canceled)
{
	for (auto& errorEntry : mFailedRequests)
	{
		if (!errorEntry.mErrorMessages.IsEmpty())
		{
			for (auto const & errorMsg : errorEntry.mErrorMessages)
				passInstance->Fail(StrFormat("Module '%s' failed during codegen with: %s", errorEntry.mSrcModule->mModuleName.c_str(), errorMsg.c_str()));
		}
		else if (!canceled)
			passInstance->Fail(StrFormat("Failed to create %s", errorEntry.mOutFileName.c_str()));
		// mHadBuildError forces it to build again
		errorEntry.mSrcModule->mHadBuildError = true;
	}

	mFailedRequests.Clear();

	bool showedCacheError = false;
	for (auto& dirEntryPair : mDirectoryCache)
	{
		auto dirEntry = dirEntryPair.mValue;
		if (!dirEntry->mError.empty())
		{
			if (!showedCacheError)
			{
				passInstance->Fail(dirEntry->mError);
				showedCacheError = true;
			}
			dirEntry->mError.clear();
		}
	}
}

BfCodeGenDirectoryData * BfCodeGen::GetDirCache(const StringImpl & cacheDir)
{
	BfCodeGenDirectoryData* dirCache = NULL;
	BfCodeGenDirectoryData** dirCachePtr = NULL;
	if (mDirectoryCache.TryAdd(cacheDir, NULL, &dirCachePtr))
	{
		dirCache = new BfCodeGenDirectoryData();
		*dirCachePtr = dirCache;
		dirCache->mCodeGen = this;
		dirCache->mDirectoryName = cacheDir;
		if (!mDisableCacheReads)
			dirCache->Read();
	}
	else
	{
		dirCache = *dirCachePtr;
	}
	return dirCache;
}

void BfCodeGen::Cancel()
{
	for (auto thread : mThreads)
	{
		thread->mShuttingDown = true;
	}

	mRequestEvent.Set(true);

	if (mIsUsingReleaseThunk)
		mCancelFunc();
}

void BfCodeGen::ClearResults()
{
	mFailedRequests.Clear();
	mCodeGenFiles.Clear();
}

bool BfCodeGen::Finish()
{
	BP_ZONE("BfCodeGen::Finish");

	while (mRequests.size() != 0)
	{
		auto request = mRequests[0];
		if (request->mResult.mType == BfCodeGenResult_NotDone)
		{
			bool hasRunningThreads = false;
			for (auto thread : mThreads)
			{
				if (thread->mRunning)
					hasRunningThreads = true;
			}
			if (!hasRunningThreads)
			{
				mPendingRequests.Clear();
				request->mResult.mType = BfCodeGenResult_Aborted;
				continue;
			}

			mDoneEvent.WaitFor(20);
			continue;
		}

		RequestComplete(request);
		delete request;
		mRequests.RemoveAt(0);
		return false;
	}

	if (mIsUsingReleaseThunk)
	{
		// Make the thunk release its threads (and more important, its LLVM contexts)
		mFinishFunc();
	}

	// We need to shut down these threads to remove their memory
	for (auto thread : mThreads)
	{
		thread->mShuttingDown = true;
		mOldThreads.push_back(thread);
	}
	mThreads.Clear();
	mRequestEvent.Set(true);

	ClearOldThreads(false);

// 	for (auto request : mPendingRequests)
// 	{
// 		if (request->mExternResultPtr != NULL)
// 		{
// 			request->mExternResultPtr->mType = BfCodeGenResult_Aborted;
// 			//delete request;
// 		}
// 		//request->mResult.mType = BfCodeGenResult_Aborted;
// 		//delete request;
// 	}
// 	mPendingRequests.Clear();

	///

	for (auto& cachePair : mDirectoryCache)
	{
		auto cacheDir = cachePair.mValue;
		cacheDir->Write();
		cacheDir->mVerified = false;
	}

	mRequestEvent.Reset();
	mRequestIdx = 0;
	return true;
}

//////////////////////////////////////////////////////////////////////////

static BfCodeGen* gExternCodeGen = NULL;

BF_EXPORT void BF_CALLTYPE Targets_Create();
BF_EXPORT void BF_CALLTYPE Targets_Delete();

static void GetExternCodeGen()
{
	if (gExternCodeGen == NULL)
	{
		gExternCodeGen = new BfCodeGen();
		Targets_Create();
	}
}

BF_EXPORT int BF_CALLTYPE BfCodeGen_GetVersion()
{
	return BF_CODEGEN_VERSION;
}

BF_EXPORT void BF_CALLTYPE BfCodeGen_ClearCache()
{
	GetExternCodeGen();
	gExternCodeGen->ClearBuildCache();
}

BF_EXPORT void BF_CALLTYPE BfCodeGen_Finish()
{
	if (gExternCodeGen != NULL)
	{
		gExternCodeGen->Finish();
		gExternCodeGen->ClearResults();
	}
}

BF_EXPORT void BF_CALLTYPE BfCodeGen_Kill()
{
	delete gExternCodeGen;
	gExternCodeGen = NULL;
	Targets_Delete();
}

BF_EXPORT void BF_CALLTYPE BfCodeGen_Cancel()
{
	if (gExternCodeGen != NULL)
		gExternCodeGen->Cancel();
}

BF_EXPORT void BF_CALLTYPE BfCodeGen_GenerateObj(const void* ptr, int size, const char* outFileName, BfCodeGenResult* resultPtr, const BfCodeGenOptions& options)
{
	GetExternCodeGen();

	BfCodeGenRequest* codeGenRequest = new BfCodeGenRequest();
	codeGenRequest->mOptions = options;
	gExternCodeGen->DoWriteObjectFile(codeGenRequest, ptr, size, outFileName, resultPtr);
}