#pragma warning(disable:4996)

//#define BFBUILD_MAIN_THREAD_COMPILE

#include "BootApp.h"
#include <iostream>
#include "BeefySysLib/util/String.h"
#include "BeefySysLib/util/FileEnumerator.h"
#include "BeefySysLib/util/WorkThread.h"
#include "BeefySysLib/platform/PlatformHelper.h"
#include "Compiler/BfSystem.h"

#ifdef BF_PLATFORM_WINDOWS
#include <direct.h>
#endif

BF_IMPORT void BF_CALLTYPE Targets_Create();
BF_IMPORT void BF_CALLTYPE Targets_Delete();

BF_IMPORT void BF_CALLTYPE BfSystem_ReportMemory(void* bfSystem);
BF_EXPORT void BF_CALLTYPE BfCompiler_ProgramDone();

BF_IMPORT void BF_CALLTYPE Debugger_FullReportMemory();

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

BF_IMPORT void BF_CALLTYPE BfCompiler_Delete(void* bfCompiler);
BF_EXPORT void BF_CALLTYPE BfCompiler_SetOptions(void* bfCompiler, void* hotProject, int hotIdx,
	const char* targetTriple, const char* targetCPU, int toolsetType, int simdSetting, int allocStackCount, int maxWorkerThreads,
	Beefy::BfCompilerOptionFlags optionFlags, const char* mallocLinkName, const char* freeLinkName);
BF_IMPORT void BF_CALLTYPE BfCompiler_ClearBuildCache(void* bfCompiler);
BF_IMPORT bool BF_CALLTYPE BfCompiler_Compile(void* bfCompiler, void* bfPassInstance, const char* outputPath);
BF_IMPORT float BF_CALLTYPE BfCompiler_GetCompletionPercentage(void* bfCompiler);
BF_IMPORT const char* BF_CALLTYPE BfCompiler_GetUsedOutputFileNames(void* bfCompiler, void* bfProject, bool flushQueuedHotFiles, bool* hadOutputChanges);

BF_IMPORT void* BF_CALLTYPE BfSystem_CreateParser(void* bfSystem, void* bfProject);
BF_IMPORT void BF_CALLTYPE BfParser_SetSource(void* bfParser, const char* data, int length, const char* fileName);
BF_IMPORT void BF_CALLTYPE BfParser_SetCharIdData(void* bfParser, uint8* data, int length);
BF_IMPORT bool BF_CALLTYPE BfParser_Parse(void* bfParser, void* bfPassInstance, bool compatMode);
BF_IMPORT bool BF_CALLTYPE BfParser_Reduce(void* bfParser, void* bfPassInstance);
BF_IMPORT bool BF_CALLTYPE BfParser_BuildDefs(void* bfParser, void* bfPassInstance, void* resolvePassData, bool fullRefresh);

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

BF_IMPORT void* BF_CALLTYPE BfSystem_Create();
BF_EXPORT void BF_CALLTYPE BfSystem_Lock(void* bfSystem, int priority);
BF_EXPORT void BF_CALLTYPE BfSystem_Unlock(void* bfSystem);
BF_IMPORT void BF_CALLTYPE BfSystem_ReportMemory(void* bfSystem);
BF_IMPORT void BF_CALLTYPE BfSystem_Delete(void* bfSystem);
BF_IMPORT void* BF_CALLTYPE BfSystem_CreatePassInstance(void* bfSystem);
BF_IMPORT void* BF_CALLTYPE BfSystem_CreateCompiler(void* bfSystem, bool isResolveOnly);
BF_IMPORT void* BF_CALLTYPE BfSystem_CreateProject(void* bfSystem, const char* projectName, const char* projectDir);
BF_IMPORT void BF_CALLTYPE BfParser_Delete(void* bfParser);
BF_IMPORT void BF_CALLTYPE BfSystem_AddTypeOptions(void* bfSystem, const char* filter, int32 simdSetting, int32 optimizationLevel, int32 emitDebugInfo, int32 arrayBoundsCheck,
	int32 initLocalVariables, int32 emitDynamicCastCheck, int32 emitObjectAccessCheck, int32 allocStackTraceDepth);

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

BF_IMPORT void BF_CALLTYPE BfProject_SetDisabled(void* bfProject, bool disabled);
BF_IMPORT void BF_CALLTYPE BfProject_SetOptions(void* bfProject, int targetType, const char* startupObject, const char* preprocessorMacros,
	int optLevel, int ltoType, int relocType, int picLevel, int32 flags);
BF_IMPORT void BF_CALLTYPE BfProject_ClearDependencies(void* bfProject);
BF_IMPORT void BF_CALLTYPE BfProject_AddDependency(void* bfProject, void* depProject);

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

BF_IMPORT const char* BF_CALLTYPE BfPassInstance_PopOutString(void* bfPassInstance);
BF_IMPORT void BF_CALLTYPE BfPassInstance_Delete(void* bfPassInstance);

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

BF_IMPORT const char* BF_CALLTYPE VSSupport_Find();

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

USING_NS_BF;

BootApp* Beefy::gApp = NULL;
uint32 gConsoleFGColor = 0;
uint32 gConsoleBGColor = 0;

static bool GetConsoleColor(uint32& fgColor, uint32& bgColor)
{
#ifdef _WIN32
	static uint32 consoleColors[16] = { 0xff000000, 0xff000080, 0xff008000, 0xff008080, 0xff800000, 0xff800080, 0xff808000, 0xffc0c0c0,
		0xff808080, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff };

	CONSOLE_SCREEN_BUFFER_INFO screenBuffInfo = { 0 };	
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &screenBuffInfo);
	fgColor = consoleColors[screenBuffInfo.wAttributes & 0xF];
	bgColor = consoleColors[(screenBuffInfo.wAttributes >> 4) & 0xF];	
	return true;
#else
	fgColor = 0xFF808080;
	bgColor = 0xFF000000;
	return false;
#endif	
}

static WORD GetColorCode(uint32 color)
{
	WORD code = 0;
#ifdef _WIN32	
	if (((color >> 0) & 0xFF) > 0x40)
		code |= FOREGROUND_BLUE;
	if (((color >> 8) & 0xFF) > 0x40)
		code |= FOREGROUND_GREEN;
	if (((color >> 16) & 0xFF) > 0x40)
		code |= FOREGROUND_RED;
	if ((((color >> 0) & 0xFF) > 0xC0) ||
		(((color >> 8) & 0xFF) > 0xC0) ||
		(((color >> 16) & 0xFF) > 0xC0))
		code |= FOREGROUND_INTENSITY;
#endif
	return code;
}

static bool SetConsoleColor(uint32 fgColor, uint32 bgColor)
{
#ifdef _WIN32
	WORD attr = GetColorCode(fgColor) | (GetColorCode(bgColor) << 4);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), attr);
	SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), attr);
	return true;
#else
	return false;
#endif
}


BootApp::BootApp()
{	
	Targets_Create();
	
	mVerbosity = Verbosity_Normal;
	mTargetType = BfTargetType_BeefConsoleApplication;

	//char str[MAX_PATH];
	//GetModuleFileNameA(NULL, str, MAX_PATH);
	//mInstallDir = GetFileDir(str) + "/";

	//getcwd(str, MAX_PATH);
	//mStartupDir = str;
	//mStartupDir += "/";

	//mDoClean = false;
	mHadCmdLine = false;
	mShowedHelp = false;
	mHadErrors = false;

	mSystem = NULL;
	mCompiler = NULL;
	mProject = NULL;	
	mCELibProject = NULL;
	mIsCERun = false;
	mAsmKind = BfAsmKind_None;
	mStartupObject = "Program";

#ifdef BF_PLATFORM_WINDOWS
	mOptLevel = BfOptLevel_OgPlus;
	mToolset = BfToolsetType_Microsoft;
#else
	mOptLevel = BfOptLevel_O0;
	mToolset = BfToolsetType_GNU;
#endif
	mEmitIR = false;	

#ifdef BF_PLATFORM_WINDOWS
	mTargetTriple = "x86_64-pc-windows-msvc";
#elif defined BF_PLATFORM_MACOS
	mTargetTriple = "x86_64-apple-macosx10.8.0";
#else
	mTargetTriple = "x86_64-unknown-linux-gnu";
#endif
	
	GetConsoleColor(gConsoleFGColor, gConsoleBGColor);
}

BootApp::~BootApp()
{
	Targets_Delete();
}

void BootApp::OutputLine(const String& text, OutputPri outputPri)
{
	if (mLogFile.IsOpen())
	{
		mLogFile.WriteSNZ(text);
		mLogFile.WriteSNZ("\n");
	}

	if (outputPri == OutputPri_Error)
		mHadErrors = true;

	switch (outputPri)
	{
	case OutputPri_Low:
		if (mVerbosity < Verbosity_Detailed)
			return;
		break;
	case OutputPri_Normal:
		if (mVerbosity < Verbosity_Normal)
			return;
		break;
	case OutputPri_High:
	case OutputPri_Warning:
	case OutputPri_Error:
		if (mVerbosity < Verbosity_Minimal)
			return;
		break;
	default: break;
	}

	if (outputPri == OutputPri_Warning)
	{
		SetConsoleColor(0xFFFFFF00, gConsoleBGColor);
		std::cerr << text.c_str() << std::endl;
		SetConsoleColor(gConsoleFGColor, gConsoleBGColor);
	}
	else if (outputPri == OutputPri_Error)
	{
		SetConsoleColor(0xFFFF0000, gConsoleBGColor);
		std::cerr << text.c_str() << std::endl;
		SetConsoleColor(gConsoleFGColor, gConsoleBGColor);
	}
	else
		std::cout << text.c_str() << std::endl;
}

void BootApp::Fail(const String& error)
{
	if (mLogFile.IsOpen())
		mLogFile.WriteSNZ("FAIL: " + error + "\n");
	std::cerr << "FAIL: " << error.c_str() << std::endl;
	mHadErrors = true;
}

bool BootApp::HandleCmdLine(const String &cmd, const String& param)
{
	mHadCmdLine = true;

	bool wantedParam = false;

	if ((!cmd.StartsWith("-")) && (mIsCERun) && (mCESrc.IsEmpty()))
	{
		mCESrc = cmd;
		return true;
	}

	if ((cmd == "-help") || (cmd == "-h") || (cmd == "/?"))
	{
		mShowedHelp = true;
		std::cout << "BeefBoot - Beef bootstrapping tool" << std::endl;
		
		return false;
	}
	else if (cmd == "-src")
	{
		mRequestedSrc.Add(param);
		wantedParam = true;
	}	
	else if (cmd == "-verbosity")
	{
		if (param == "quiet")
			mVerbosity = Verbosity_Quiet;
		else if (param == "minimal")
			mVerbosity = Verbosity_Minimal;
		else if (param == "normal")
			mVerbosity = Verbosity_Normal;
		else if (param == "detailed")
			mVerbosity = Verbosity_Detailed;
		else if (param == "diagnostic")
			mVerbosity = Verbosity_Diagnostic;
		else
		{
			Fail(StrFormat("Invalid verbosity level: '%s'", param.c_str()));
			return false;
		}
		wantedParam = true;
	}
	else if (cmd == "-version")
	{
		BfpSystemResult sysResult;
		String exePath;
		BFP_GETSTR_HELPER(exePath, sysResult, BfpSystem_GetExecutablePath(__STR, __STRLEN, &sysResult));

		std::cout << "0.0.0" << std::endl;
		mShowedHelp = true;
		return true;
	}
	else if (cmd == "-define")
	{
		if (!mDefines.IsEmpty())
			mDefines += "\n";
		mDefines += param;
		wantedParam = true;
	}
	else if (cmd == "-startup")
	{
		mStartupObject = param;
		wantedParam = true;
	}
	else if (cmd == "-out")
	{
		mTargetPath = param;
		wantedParam = true;
	}
	else if (cmd == "-linkparams")
	{
		mLinkParams = param;
		wantedParam = true;
	}
	else if (cmd == "-Og+")
	{
		mOptLevel = BfOptLevel_OgPlus;
	}
	else if (cmd == "-O0")
	{
		mOptLevel = BfOptLevel_O0;
	}
	else if (cmd == "-O1")
	{
		mOptLevel = BfOptLevel_O1;
	}
	else if (cmd == "-O2")
	{
		mOptLevel = BfOptLevel_O2;
	}
	else if (cmd == "-O3")
	{
		mOptLevel = BfOptLevel_O3;
	}
	else if (cmd == "-gnu")
	{
		mToolset = BfToolsetType_GNU;
	}
	else if (cmd == "-emitir")
	{
		mEmitIR = true;
	}	
	else if (cmd == "-cedest")
	{
		mIsCERun = true;
		mCEDest = param;
		wantedParam = true;
	}
	else if (cmd == "-cesrc")
	{
		mIsCERun = true;
	}
	else if (cmd == "-emitasm")
	{
		if (param.IsEmpty())
		{
			mAsmKind = BfAsmKind_Intel;
		}
		else
		{
			if (param == "att")
				mAsmKind = BfAsmKind_ATT;
			else
				mAsmKind = BfAsmKind_Intel;
			wantedParam = true;
		}				
	}
	else
	{
		Fail("Unknown option: " + cmd);
		return false;
	}

	if ((wantedParam) && (param.empty()))
	{
		Fail(StrFormat("Parameter expected for '%s'", cmd.c_str()));
		return false;
	}
	else if ((!wantedParam) && (!param.empty()))
	{
		Fail(StrFormat("No parameter expected for '%s'", cmd.c_str()));
		return false;
	}

	return true;
}

bool BootApp::Init()
{    
	char* cwdPtr = getcwd(NULL, 0);
	mWorkingDir = cwdPtr;
	free(cwdPtr);

	if ((mTargetPath.IsEmpty()) && (mCESrc.IsEmpty()))
	{
		Fail("'Out' path not specified");
	}

	if (mRequestedSrc.IsEmpty())
	{
		Fail("No source specified");
	}

	return !mHadErrors;
}

void BootApp::QueueFile(const StringImpl& path, void* project)
{
	String ext;
	ext = GetFileExtension(path);
    if ((ext.Equals(".bf", StringImpl::CompareKind_OrdinalIgnoreCase)) ||
        (ext.Equals(".cs", StringImpl::CompareKind_OrdinalIgnoreCase)))
	{		
		int len;
		const char* data = LoadTextData(path, &len);
		if (data == NULL)
		{
			Fail(StrFormat("Unable to load file '%s'", path.c_str()));
			return;
		}
		
		bool worked = true;
		void* bfParser = BfSystem_CreateParser(mSystem, project);
		BfParser_SetSource(bfParser, data, len, path.c_str());
		//bfParser.SetCharIdData(charIdData);
		worked &= BfParser_Parse(bfParser, mPassInstance, false);
		worked &= BfParser_Reduce(bfParser, mPassInstance);
		worked &= BfParser_BuildDefs(bfParser, mPassInstance, NULL, false);
		
		delete data;
	}
}

void BootApp::QueuePath(const StringImpl& path)
{
	if (DirectoryExists(path))
	{
		for (auto& fileEntry : FileEnumerator(path, FileEnumerator::Flags_Files))
		{
			String filePath = fileEntry.GetFilePath();
			
			String fileName;
			fileName = GetFileName(filePath);

			QueueFile(filePath, (mCELibProject != NULL) ? mCELibProject : mProject);
		}

		for (auto& fileEntry : FileEnumerator(path, FileEnumerator::Flags_Directories))
		{
			String childPath = fileEntry.GetFilePath();
			String dirName;
			dirName = GetFileName(childPath);

			if (dirName == "build")
				continue;

			QueuePath(childPath);
		}
	}
	else
	{
		QueueFile(path, mProject);
	}
}

static void CompileThread(void* param)
{
	BfpThread_SetName(NULL, "CompileThread", NULL);

	BootApp* app = (BootApp*)param;
	BfCompiler_ClearBuildCache(app->mCompiler);
	
	if (!BfCompiler_Compile(app->mCompiler, app->mPassInstance, app->mBuildDir.c_str()))
		app->mHadErrors = true;
}

void BootApp::DoCompile()
{
#ifdef BFBUILD_MAIN_THREAD_COMPILE
	mOutputDirectory = outputDirectory;
	CompileThread(this);
#else	

	WorkThreadFunc workThread;
	workThread.Start(CompileThread, this);

	int lastProgressTicks = 0;

	bool showProgress = mVerbosity >= Verbosity_Normal;

	int progressSize = 30;
	if (showProgress)
	{
		std::cout << "[";
		for (int i = 0; i < progressSize; i++)
			std::cout << " ";
		std::cout << "]";
		for (int i = 0; i < progressSize + 1; i++)
			std::cout << "\b";
		std::cout.flush();
	}

	while (true)
	{
		bool isDone = workThread.WaitForFinish(100);

		float pct = BfCompiler_GetCompletionPercentage(mCompiler);
		if (isDone)
			pct = 1.0;
		int progressTicks = (int)(pct * progressSize + 0.5f);

		while (progressTicks > lastProgressTicks)
		{
			if (showProgress)
			{
				std::cout << "*";
				std::cout.flush();
			}
			lastProgressTicks++;
		}

		if (isDone)
			break;
	}

	if (showProgress)
		std::cout << std::endl;
#endif
}

struct OutputContext
{
	bool mIsError;
	BfpFile* mFile;
};

static void OutputThread(void* param)
{
	OutputContext* context = (OutputContext*)param;

	String queuedStr;
	
	while (true)
	{
		char data[1024];
		
		BfpFileResult result;
		int bytesRead = (int)BfpFile_Read(context->mFile, data, 1023, -1, &result);
		if ((result != BfpFileResult_Ok) && (result != BfpFileResult_PartialData))
			return;

		data[bytesRead] = 0;
		if (context->mIsError)
		{
			std::cerr << data;
			std::cerr.flush();
		}
		else
		{
			std::cout << data;
			std::cout.flush();
		}

		if (gApp->mLogFile.IsOpen())
		{
			// This is to ensure that error and output lines are not merged together, though they may interleave
			queuedStr.Append(data, bytesRead);
			while (true)
			{
				int crPos = (int)queuedStr.IndexOf('\n');
				if (crPos == -1)
					break;

				AutoCrit autoCrit(gApp->mLogCritSect);
				if (context->mIsError)
					gApp->mLogFile.WriteSNZ("err> ");
				else
					gApp->mLogFile.WriteSNZ("out> ");

				int endPos = crPos;
				if ((endPos > 0) && (queuedStr[endPos - 1] == '\r'))
					endPos--;
				gApp->mLogFile.Write((void*)queuedStr.c_str(), endPos);
				gApp->mLogFile.WriteSNZ("\n");
				queuedStr.Remove(0, crPos + 1);
			}
		}
	}
}

bool BootApp::QueueRun(const String& fileName, const String& args, const String& workingDir, BfpSpawnFlags extraFlags)
{
	OutputLine(StrFormat("EXECUTING: %s %s", fileName.c_str(), args.c_str()), OutputPri_Low);

    BfpSpawnFlags spawnFlags = (BfpSpawnFlags)(BfpSpawnFlag_NoWindow | BfpSpawnFlag_RedirectStdOutput | BfpSpawnFlag_RedirectStdError | extraFlags);    
    BfpSpawn* spawn = BfpSpawn_Create(fileName.c_str(), args.c_str(), workingDir.c_str(), NULL, spawnFlags, NULL);
    if (spawn == NULL)
    {
        Fail(StrFormat("Failed to execute '%s'", fileName.c_str()));
        return false;
    }
    int exitCode = 0;

	OutputContext outputContext;;
	outputContext.mIsError = false;
	OutputContext errorContext;	
	errorContext.mIsError = false;
	BfpSpawn_GetStdHandles(spawn, NULL, &outputContext.mFile, &errorContext.mFile);

	BfpThread* outputThread = BfpThread_Create(OutputThread, (void*)&outputContext);
	BfpThread* errorThread = BfpThread_Create(OutputThread, (void*)&errorContext);
	
    BfpSpawn_WaitFor(spawn, -1, &exitCode, NULL);	

	if (outputContext.mFile != NULL)
		BfpFile_Close(outputContext.mFile, NULL);
	if (errorContext.mFile != NULL)
		BfpFile_Close(errorContext.mFile, NULL);

	BfpThread_WaitFor(outputThread, -1);
	BfpThread_WaitFor(errorThread, -1);	

	BfpThread_Release(outputThread);
	BfpThread_Release(errorThread);
    BfpSpawn_Release(spawn);

    if (exitCode != 0)
    {
        Fail(StrFormat("Exit code returned: %d", exitCode));
        return false;
    }
    return true;
}

bool BootApp::CopyFile(const StringImpl& srcPath, const StringImpl& destPath)
{
	BfpFileResult result = BfpFileResult_Ok;
	for (int i = 0; i < 20; i++)
	{
		BfpFile_Copy(srcPath.c_str(), destPath.c_str(), BfpFileCopyKind_Always, &result);
		if (result == BfpFileResult_Ok)
			return true;
		BfpThread_Sleep(100);
	}
	Fail(StrFormat("Failed to copy '%s' to '%s'", srcPath.c_str(), destPath.c_str()));
	return false;
}

#ifdef BF_PLATFORM_WINDOWS
void BootApp::DoLinkMS()
{
	String vsStr = VSSupport_Find();
		
	int toolIdx = (int)vsStr.IndexOf("TOOL64\t");
	int toolCrIdx = (int)vsStr.IndexOf('\n', toolIdx + 1);
	if ((toolIdx == -1) || (toolCrIdx == -1))
	{
		Fail("Failed to detect Visual Studio configuration. Is Visual Studio 2015 or later installed?");
		return;
	}
	
	String linkerPath = vsStr.Substring(toolIdx + 7, toolCrIdx - toolIdx - 7);
	linkerPath.Append("\\link.exe");

	String linkLine;

	String targetPath = mTargetPath;

	bool hadOutputChanges;

	BfSystem_Lock(mSystem, 0);
	const char* result = BfCompiler_GetUsedOutputFileNames(mCompiler, mProject, true, &hadOutputChanges);
	BfSystem_Unlock(mSystem);
	
	if (result == NULL)
		return;
	std::string fileNamesStr;
	fileNamesStr += result;
	if (fileNamesStr.length() == 0)
		return;
	int curIdx = -1;
	while (curIdx < (int)fileNamesStr.length())
	{
		int nextBr = (int)fileNamesStr.find('\n', curIdx + 1);
		if (nextBr == -1)
			nextBr = (int)fileNamesStr.length();
		linkLine.Append(fileNamesStr.substr(curIdx + 1, nextBr - curIdx - 1));
		linkLine.Append(" ");
		curIdx = nextBr;
	}

	linkLine.Append("-out:");
	IDEUtils::AppendWithOptionalQuotes(linkLine, targetPath);
	linkLine.Append(" ");

	if (mTargetType == BfTargetType_BeefConsoleApplication)
		linkLine.Append("-subsystem:console ");
	else
		linkLine.Append("-subsystem:windows ");

	linkLine.Append("-defaultlib:libcmtd ");
	linkLine.Append("-nologo ");

	linkLine.Append("-pdb:");
	int lastDotPos = (int)targetPath.LastIndexOf('.');
    if (lastDotPos == -1)
        lastDotPos = (int)targetPath.length();
	auto pdbName = String(targetPath, 0, lastDotPos);
	pdbName.Append(".pdb");
	IDEUtils::AppendWithOptionalQuotes(linkLine, pdbName);
	linkLine.Append(" ");

	linkLine.Append("-debug ");

	int checkIdx = 0;
	while (true)
	{
		int libIdx = (int)vsStr.IndexOf("LIB64\t", checkIdx);
		if (libIdx == -1)
			break;
		int libCrIdx = (int)vsStr.IndexOf('\n', libIdx + 1);
		if (libCrIdx == -1)
			break;

		String libPath = vsStr.Substring(libIdx + 6, libCrIdx - libIdx - 6);
		linkLine.Append("-libpath:\"");
		linkLine.Append(libPath);
		linkLine.Append("\" ");
		checkIdx = libCrIdx + 1;
	}

	linkLine.Append(mLinkParams);	

	BfpSpawnFlags flags = BfpSpawnFlag_None;			
	flags = (BfpSpawnFlags)(BfpSpawnFlag_UseArgsFile | BfpSpawnFlag_UseArgsFile_Native | BfpSpawnFlag_UseArgsFile_BOM);			

	auto runCmd = QueueRun(linkerPath, linkLine, mWorkingDir, flags);
}
#endif

void BootApp::DoLinkGNU()
{
    String linkerPath;
    if (FileExists("/usr/bin/clang++"))
    	linkerPath = "/usr/bin/clang++";
    else
    	linkerPath = "/usr/bin/c++";

    String linkLine;

    String targetPath = mTargetPath;

    bool hadOutputChanges;
    const char* result = BfCompiler_GetUsedOutputFileNames(mCompiler, mProject, true, &hadOutputChanges);
    if (result == NULL)
        return;
    std::string fileNamesStr;
    fileNamesStr += result;
    if (fileNamesStr.length() == 0)
        return;
    int curIdx = -1;
    while (curIdx < (int)fileNamesStr.length())
    {
        int nextBr = (int)fileNamesStr.find('\n', curIdx + 1);
        if (nextBr == -1)
            nextBr = (int)fileNamesStr.length();
        linkLine.Append(fileNamesStr.substr(curIdx + 1, nextBr - curIdx - 1));
        linkLine.Append(" ");
        curIdx = nextBr;
    }

    linkLine.Append("-o ");
    IDEUtils::AppendWithOptionalQuotes(linkLine, targetPath);
    linkLine.Append(" ");
    linkLine.Append("-g ");

    linkLine.Append("-debug ");
#ifdef BF_PLATFORM_LINUX
	linkLine.Append("-no-pie ");
#endif

    linkLine.Append(mLinkParams);

    auto runCmd = QueueRun(linkerPath, linkLine, mWorkingDir, BfpSpawnFlag_UseArgsFile);
}

bool BootApp::Compile()
{
	DWORD startTick = BFTickCount();

	mSystem = BfSystem_Create();

	mCompiler = BfSystem_CreateCompiler(mSystem, false);

	String projectName = GetFileName(mTargetPath);
	int dotPos = (int)projectName.IndexOf('.');
	if (dotPos != -1)
		projectName.RemoveToEnd(dotPos);
	if (projectName.IsEmpty())
		projectName.Append("BeefProject");
	
	mProject = BfSystem_CreateProject(mSystem, projectName.c_str(), GetFileDir(mTargetPath).c_str());
	
	if (mIsCERun)
	{
		mCELibProject = BfSystem_CreateProject(mSystem, "BeefLib", GetFileDir(mTargetPath).c_str());

		BfProjectFlags flags = BfProjectFlags_None;
		BfProject_SetOptions(mCELibProject, BfTargetType_BeefLib, "", mDefines.c_str(), mOptLevel, 0, 0, 0, flags);		
	}

	if (!mDefines.IsEmpty())
		mDefines.Append("\n");
	mDefines.Append("BF_64_BIT");
	mDefines.Append("\nBF_LITTLE_ENDIAN");
	mDefines.Append("\n");
	mDefines.Append(BF_PLATFORM_NAME);

	int ltoType = 0;
	BfProjectFlags flags = BfProjectFlags_None;
	if (mIsCERun)
	{
		flags = (BfProjectFlags)(flags | BfProjectFlags_SingleModule | BfProjectFlags_AlwaysIncludeAll);
		if (mAsmKind == BfAsmKind_ATT)
			flags = (BfProjectFlags)(flags | BfProjectFlags_AsmOutput | BfProjectFlags_AsmOutput_ATT);
		else if (mAsmKind == BfAsmKind_Intel)
			flags = (BfProjectFlags)(flags | BfProjectFlags_AsmOutput);
	}
    BfProject_SetOptions(mProject, mTargetType, mStartupObject.c_str(), mDefines.c_str(), mOptLevel, ltoType, 0, 0, flags);

	if (mCELibProject != NULL)
		BfProject_AddDependency(mProject, mCELibProject);
	
	mPassInstance = BfSystem_CreatePassInstance(mSystem);
	
	Beefy::String exePath;
	BfpGetStrHelper(exePath, [](char* outStr, int* inOutStrSize, BfpResult* result)
		{
			BfpSystem_GetExecutablePath(outStr, inOutStrSize, (BfpSystemResult*)result);
		});
	mBuildDir = GetFileDir(exePath) + "/build";
	
	RecursiveCreateDirectory(mBuildDir + "/" + projectName);
	if (mIsCERun)
		RecursiveCreateDirectory(mBuildDir + "/BeefLib");

	BfCompilerOptionFlags optionFlags = (BfCompilerOptionFlags)(BfCompilerOptionFlag_EmitDebugInfo | BfCompilerOptionFlag_EmitLineInfo | BfCompilerOptionFlag_GenerateOBJ | BfCompilerOptionFlag_OmitDebugHelpers);
	if (mEmitIR)
		optionFlags = (BfCompilerOptionFlags)(optionFlags | BfCompilerOptionFlag_WriteIR);

	int maxWorkerThreads = BfpSystem_GetNumLogicalCPUs(NULL);
	if (maxWorkerThreads <= 1)
		maxWorkerThreads = 6;

    BfCompiler_SetOptions(mCompiler, NULL, 0, mTargetTriple.c_str(), "", mToolset, BfSIMDSetting_SSE2, 1, maxWorkerThreads, optionFlags, "malloc", "free");
	    
	if (mIsCERun)
	{
		QueueFile(mCESrc, mProject);
	}
	for (auto& srcName : mRequestedSrc)
	{
		String absPath = GetAbsPath(srcName, mWorkingDir);
		QueuePath(absPath);
	}

	if (!mHadErrors)
	{
		DoCompile();
		OutputLine(StrFormat("TIMING: Beef compiling: %0.1fs", (BFTickCount() - startTick) / 1000.0), OutputPri_Normal);

		if (!mCEDest.IsEmpty())
		{
			String ext;
			String srcResult = mBuildDir + "/BeefProject/BeefProject";
			if (mAsmKind == BfAsmKind_None)			
				srcResult += BF_OBJ_EXT;
			else			
				srcResult += ".s";			
			
			CopyFile(srcResult, mCEDest);			
		}

		if ((mIsCERun) && (mEmitIR))
		{
			String ext;			
			String srcResult = mBuildDir + "/BeefProject/BeefProject";			
			String irDestPath = mCEDest;
			int dotPos = (int)irDestPath.LastIndexOf('.');
			if (dotPos != -1)
				irDestPath.RemoveToEnd(dotPos);

			if (mOptLevel == BfOptLevel_OgPlus)
			{
				srcResult += ".beir";
				irDestPath += ".ll";
			}
			else
			{
				srcResult += ".ll";			
				irDestPath += ".ll";
			}
			
			CopyFile(srcResult, irDestPath);			
		}
	}

	while (true)
	{
		const char* msg = BfPassInstance_PopOutString(mPassInstance);
		if (msg == NULL)
			break;

		if ((strncmp(msg, ":warn ", 6) == 0))
		{
			OutputLine(msg + 6, OutputPri_Warning);
		}
		else if ((strncmp(msg, ":error ", 7) == 0))
		{
			OutputLine(msg + 7, OutputPri_Error);
		}
		else if ((strncmp(msg, ":med ", 5) == 0))
		{
			OutputLine(msg + 5, OutputPri_Normal);
		}
		else if ((strncmp(msg, ":low ", 5) == 0))
		{
			OutputLine(msg + 5, OutputPri_Low);
		}
		else if ((strncmp(msg, "ERROR(", 6) == 0) || (strncmp(msg, "ERROR:", 6) == 0))
		{
			OutputLine(msg, OutputPri_Error);
		}
		else if ((strncmp(msg, "WARNING(", 8) == 0) || (strncmp(msg, "WARNING:", 8) == 0))
		{
			OutputLine(msg, OutputPri_Warning);
		}
		else
			OutputLine(msg);
	}
		
	if ((!mHadErrors) && (!mTargetPath.IsEmpty()))
    {
		if (mVerbosity == Verbosity_Normal)
		{
			std::cout << "Linking " << mTargetPath.c_str() << "...";
			std::cout.flush();
		}

#ifdef BF_PLATFORM_WINDOWS
        DoLinkMS();
#else
        DoLinkGNU();
#endif

		if (mVerbosity == Verbosity_Normal)
			std::cout << std::endl;
    }

	BfPassInstance_Delete(mPassInstance);
	BfCompiler_Delete(mCompiler);

	BfSystem_Delete(mSystem);

	return !mHadErrors;
}

