; This code is based on Ahk2Exe's Compiler.ahk
; limitations : includes MUST be in same directory as script
; Standard and User Libraries are NOT supported

BundleAhkScript(AhkFile, ExeFile, BinFile)
{
	Global f_UpxExe
	FileCopy, %BinFile%, %ExeFile%, 1
	SplitPath, AhkFile,, ScriptDir
	
	ExtraFiles := []
	PreprocessScript(ScriptBody, AhkFile, ExtraFiles)
	VarSetCapacity(BinScriptBody, BinScriptBody_Len := StrPut(ScriptBody, "UTF-8"))
	StrPut(ScriptBody, &BinScriptBody, "UTF-8")
	
	module := DllCall("BeginUpdateResource", "str", ExeFile, "uint", 0, "ptr")
	If !module
		Msg_Error("Erreur : impossible d'ouvrir le fichier de destination.")

	If !DllCall("UpdateResource", "ptr", module, "ptr", 10, "str", ">AHK WITH ICON<", "ushort", 0x409, "ptr", &BinScriptBody, "uint", BinScriptBody_Len, "uint")
		{
		Gosub _EndUpdateResource
		Msg_Error("Erreur lors de l'ajout du script.")
		}
	
	oldWD := A_WorkingDir
	SetWorkingDir, %ScriptDir%
	for each,file in ExtraFiles
	{
		IfNotExist, %file%
			{
			Gosub _EndUpdateResource
			Msg_Error("Erreur lors de l'ajout du fichier : `n`n- "file)
			}
		
		StringUpper, resname, file
		; This "old-school" method of reading binary files is way faster than using file objects.
		FileGetSize, filesize, %file%
		VarSetCapacity(filedata, filesize)
		FileRead, filedata, *c %file%
		If !DllCall("UpdateResource", "ptr", module, "ptr", 10, "str", resname
				  , "ushort", 0x409, "ptr", &filedata, "uint", filesize, "uint")
			{
			Gosub _EndUpdateResource
			Msg_Error("Erreur lors de l'ajout du fichier : `n`n- "file)
			}
		VarSetCapacity(filedata, 0)
	}
	SetWorkingDir, %oldWD%
	
	gosub _EndUpdateResource
	
	return True
	
_EndUpdateResource:
	If !DllCall("EndUpdateResource", "ptr", module, "uint", 0)
		Msg_Error("Erreur : impossible d'ouvrir le fichier de destination.")
	return
}

PreprocessScript(ByRef ScriptText, AhkScript, ExtraFiles, FileList="", FirstScriptDir="", Options="")
{
	SplitPath, AhkScript, ScriptName, ScriptDir
	If !IsObject(FileList)
	{
		FileList := [AhkScript]
		ScriptText := "; <COMPILER: v" A_AhkVersion ">`n"
		FirstScriptDir := ScriptDir
		Options := { comm: ";", esc: "``" }
		
		OldWorkingDir := A_WorkingDir
		SetWorkingDir, %ScriptDir%
	}
	
	cmtBlock := false, contSection := false
	Loop, Read, %AhkScript%
	{
		tline := Trim(A_LoopReadLine)
		If !cmtBlock
		{
			If !contSection
			{
				If (StrStartsWith(tline, Options.comm) || !tline)
					Continue
				Else If StrStartsWith(tline, "/*")
				{
					cmtBlock := true
					Continue
				}
			}
			If StrStartsWith(tline, "(")
				contSection := true
			Else If StrStartsWith(tline, ")")
				contSection := false
			
			tline := RegExReplace(tline, "\s+" RegExEscape(Options.comm) ".*$", "")
			If !contSection && RegExMatch(tline, "i)#Include(Again)?\s+(.*)$", o)
			{
				IsIncludeAgain := (o1 = "Again")
				IncludeFile := o2
				; pas tout compris cette regex ???
				If RegExMatch(IncludeFile, "\*[iI]\s+?(.*)", o)
					IncludeFile := Trim(o1)
				
				StringReplace, IncludeFile, IncludeFile, `%A_ScriptDir`%, %FirstScriptDir%, All
				; the only supported dir for includes is A_ScriptDir so no need to change dir
				If FileExist(IncludeFile) = "D"
					Continue
				
				IncludeFile := ScriptDir "\" IncludeFile
				
				AlreadyIncluded := false
				for k,v in FileList
				If (v = IncludeFile)
				{
					AlreadyIncluded := true
					Break
				}
				If(IsIncludeAgain || !AlreadyIncluded)
				{
					If !AlreadyIncluded
						FileList._Insert(IncludeFile)
					PreprocessScript(ScriptText, IncludeFile, ExtraFiles, FileList, FirstScriptDir, Options)
				}
			}
			Else If !contSection && RegExMatch(tline, "i)^FileInstall[ \t]*[, \t][ \t]*([^,]+?)[ \t]*,", o) ; TODO: implement `, detection
			{
				If o1 ~= "[^``]%"
					Msg_Error("Erreur: Syntaxe invalide ""FileInstall"".")
				_ := Options.esc
				StringReplace, o1, o1, %_%`%, `%, All
				StringReplace, o1, o1, %_%`,, `,, All
				StringReplace, o1, o1, %_%%_%,, %_%,, All
				ExtraFiles._Insert(o1)
				ScriptText .= tline "`n"
			}
			Else If !contSection && RegExMatch(tline, "i)^#CommentFlag\s+(.+)$", o)
				Options.comm := o1, ScriptText .= tline "`n"
			Else If !contSection && RegExMatch(tline, "i)^#EscapeChar\s+(.+)$", o)
				Options.esc := o1, ScriptText .= tline "`n"
			Else If !contSection && RegExMatch(tline, "i)^#DerefChar\s+(.+)$", o)
				Msg_Error("Erreur: #DerefChar n'est pas supporté.")
			Else If !contSection && RegExMatch(tline, "i)^#Delimiter\s+(.+)$", o)
				Msg_Error("Erreur: #Delimiter n'est pas supporté.")
			Else
				ScriptText .= (contSection ? A_LoopReadLine : tline) "`n"
		}
		Else If StrStartsWith(tline, "*/")
			cmtBlock := false
	}
	
	If OldWorkingDir
		SetWorkingDir, %OldWorkingDir%
}

StrStartsWith(ByRef v, ByRef w)
{
	return SubStr(v, 1, StrLen(w)) = w
}

RegExEscape(t)
{
	static _ := "\.*?+[{|()^$"
	Loop, Parse, _
		StringReplace, t, t, %A_LoopField%, \%A_LoopField%, All
	return t
}
