
task("update-globalheaders")

set_menu({
	-- Settings menu usage
	usage = "xmake update-globalheaders [options]",
	description = "Regenerate global headers for each module"
})

on_run(function ()
	local paths = {}

	local excludedFiles = {
		["Components.hpp"] = { Define = "NAZARA_ENTT" },
		["ConfigCheck.hpp"] = true,
		["Debug.hpp"] = true,
		["DebugOff.hpp"] = true,
		["Systems.hpp"] = { Define = "NAZARA_ENTT" },
		["ThreadSafety.hpp"] = true,
		["ThreadSafetyOff.hpp"] = true
	}

	local modules = os.dirs("include/Nazara/*")
	for _, modulePath in pairs(modules) do
		local moduleName = modulePath:match(".*[\\/](.*)")

		local config, err = io.open(modulePath .. "/Config.hpp", "r")
		local head = ""
		if (not config) then
			error("Failed to read config file: " .. err)
		end

		for line in config:lines() do
			if (line == "#pragma once") then -- Stop before including the #pragma once as it's already written automatically
				break
			end
			head = head .. line .. "\n"
		end

		config:close()

		paths[moduleName] = {
			Excludes = table.copy(excludedFiles),
			Header = head,
			HeaderGuard = "NAZARA_GLOBAL_" .. moduleName:upper() .. "_HPP",
			Name = "Nazara" .. moduleName,
			SearchDir = modulePath,
			Target = modulePath .. ".hpp"
		}

		-- Handle components and systems
		if (os.isdir(modulePath .. "/Components")) then
			paths[moduleName .. "_Components"] = {
				Excludes = table.copy(excludedFiles),
				Header = head,
				HeaderGuard = "NAZARA_" .. moduleName:upper() .. "_COMPONENTS_HPP",
				Name = "Nazara" .. moduleName .. " components",
				SearchDir = modulePath .. "/Components",
				Target = modulePath .. "/Components.hpp"
			}	
		end

		if (os.isdir(modulePath .. "/Systems")) then
			paths[moduleName .. "_Systems"] = {
				Excludes = table.copy(excludedFiles),
				Header = head,
				HeaderGuard = "NAZARA_" .. moduleName:upper() .. "_SYSTEMS_HPP",
				Name = "Nazara" .. moduleName .. " systems",
				SearchDir = modulePath .. "/Systems",
				Target = modulePath .. "/Systems.hpp"
			}	
		end
	end

	paths["Audio"].Excludes["OpenALFunctions.hpp"] = true
	paths["Core"].Excludes["AppEntitySystemComponent.hpp"] = { Define = "NAZARA_ENTT" }
	paths["Core"].Excludes["EnttSystemGraph.hpp"] = { Define = "NAZARA_ENTT" }
	paths["Core"].Excludes["EnttWorld.hpp"] = { Define = "NAZARA_ENTT" }
	paths["Network"].Excludes["CurlLibrary.hpp"] = true
	paths["Network"].Excludes["CurlFunctions.hpp"] = true
	paths["OpenGLRenderer"].Excludes["Wrapper.hpp"] = true
	paths["VulkanRenderer"].Excludes["Wrapper.hpp"] = true

	-- OpenGL renderer wrapper
	paths["OpenGLWrapper"] = {
		Excludes = {
			["DeviceFunctions.hpp"] = true,
			["GlobalFunctions.hpp"] = true,
			["InstanceFunctions.hpp"] = true,
		},
		Header = paths["OpenGLRenderer"].Header,
		HeaderGuard = "NAZARA_OPENGLRENDERER_WRAPPER_HPP",
		Name = "OpenGL wrapper",
		SearchDir = "include/Nazara/OpenGLRenderer/Wrapper", 
		Target = "include/Nazara/OpenGLRenderer/Wrapper.hpp"
	}

	-- Vulkan renderer wrapper
	paths["VulkanWrapper"] = {
		Excludes = {
			["DeviceFunctions.hpp"] = true,
			["GlobalFunctions.hpp"] = true,
			["InstanceFunctions.hpp"] = true,
		},
		Header = paths["VulkanRenderer"].Header,
		HeaderGuard = "NAZARA_VULKANRENDERER_WRAPPER_HPP",
		Name = "Vulkan wrapper",
		SearchDir = "include/Nazara/VulkanRenderer/Wrapper", 
		Target = "include/Nazara/VulkanRenderer/Wrapper.hpp"
	}

	for _,v in pairs(paths) do
		print(v.Name)
		local files = os.files(v.SearchDir .. "/*.hpp")
		if (#files == 0) then
			print("Skipped (no file matched)")
			goto continue
		end

		local header, err = io.open(v.Target, "w+")
		if (not header) then
			error("Failed to create header file (" .. v.Target .. "): " .. err)
		end

		header:write("// this file was automatically generated and should not be edited\n\n")
		if (v.Header) then
			header:write(v.Header)
		end

		header:write("#pragma once\n\n")
		header:write("#ifndef " .. v.HeaderGuard .. "\n")
		header:write("#define " .. v.HeaderGuard .. "\n\n")

		local gatedIncludes = {}

		local count = 0
		for _, filePath in pairs(files) do
			local pathParts = path.split(filePath)
			if (pathParts[1] == "include") then
				table.remove(pathParts, 1)
			end

			local include = table.concat(pathParts, "/")
			local fileName = path.filename(filePath)

			local exclusion = v.Excludes[fileName]
			if (not exclusion) then
				header:write("#include <" .. include .. ">\n")
				count = count + 1
			elseif (type(exclusion) == "table" and exclusion.Define) then
				local gatedFiles = gatedIncludes[exclusion.Define]
				if not gatedFiles then
					gatedFiles = {}
					gatedIncludes[exclusion.Define] = gatedFiles
				end
				table.insert(gatedFiles, include)
			end
		end
		header:write("\n")

		for _, gatedDefine in ipairs(table.orderkeys(gatedIncludes)) do
			header:write("#ifdef " .. gatedDefine .. "\n\n")
			for _, include in ipairs(gatedIncludes[gatedDefine]) do
				header:write("#include <" .. include .. ">\n")
			end
			header:write("\n#endif\n\n")
		end

		header:write("#endif // " .. v.HeaderGuard .. "\n")
		header:close()
		
		print(string.format("-#include count: %d", count))
		::continue::
	end
end)
