rule("bin2c")
    set_extensions(".bin")
    
	on_load(function (target)
        local headerdir = path.join(target:autogendir(), "rules", "bin2c")

		local outputSource = table.unpack(target:extraconf("rules", "bin2c", "outputSource"))
        
		if not os.isdir(headerdir) then
            os.mkdir(headerdir)
        end

		target:add("includedirs", headerdir)

		target:add("files", outputSource)
    end)

    before_buildcmd_files(function (target, batchcmds, sourcebatch, opt)
		local outputHeader = table.unpack(target:extraconf("rules", "bin2c", "outputHeader"))

		local outputHeaderEnumContent = ""

		for _, filePath in ipairs(sourcebatch.sourcefiles) do
			local escapedName = string.gsub(filePath, "[/|.]", "_")
			outputHeaderEnumContent = outputHeaderEnumContent .. "\t" .. escapedName .. ",\n"
		end

		local outputHeaderContent = string.format([[
#pragma once

#include <cstdint>
#include <string>

struct Asset {
	const unsigned char* data;
	std::size_t size;
};

enum class AssetName {
%s
};

const Asset& getResource(AssetName fileName);

const Asset& getResource(const std::string& fileName);
]], outputHeaderEnumContent)

        local outputSource = table.unpack(target:extraconf("rules", "bin2c", "outputSource"))

		local relativePath = path.join(path.relative(path.directory(outputHeader), path.directory(outputSource)), path.filename(outputHeader))
		
		local outputSourceContent = string.format([[
#include "%s"

#include <map>

]], relativePath)


		local outputSourceArrayVars = ""
		local outputSourceMapVars = ""
		
		for _, filePath in ipairs(sourcebatch.sourcefiles) do
			local escapedName = string.gsub(filePath, "[/|.]", "_")
			local varDecl = string.format("static const unsigned char %s[] = {\n\t#include <%s>\n};\n\n", escapedName, filePath .. ".h")
			outputSourceContent = outputSourceContent .. varDecl
			outputSourceArrayVars = outputSourceArrayVars .. string.format("\t{%s, sizeof(%s)},\n", escapedName, escapedName)
			outputSourceMapVars = outputSourceMapVars .. string.format("\t{\"%s\", AssetName::%s},\n", filePath, escapedName)
		end

		outputSourceContent = outputSourceContent .. string.format([[
static const Asset assets[] = {
%s
};

static const std::map<std::string, AssetName> assetMap = {
%s
};

]], outputSourceArrayVars, outputSourceMapVars)

		outputSourceContent = outputSourceContent .. [[
const Asset& getResource(AssetName fileName) {
	return assets[static_cast<std::size_t>(fileName)];
}

const Asset& getResource(const std::string& fileName) {
	return getResource(assetMap.at(fileName));
}
]]

		for _, sourcefile_bin in ipairs(sourcebatch.sourcefiles) do
			-- get header file
			local headerdir = path.join(target:autogendir(), "rules", "bin2c")
			local headerfile = path.join(headerdir, sourcefile_bin .. ".h")
			target:add("includedirs", headerdir)

			-- add commands
			batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", sourcefile_bin)
			batchcmds:mkdir(headerdir)
			local argv = {"lua", "private.utils.bin2c", "-i", path(sourcefile_bin), "-o", path(headerfile)}
			local linewidth = target:extraconf("rules", "bin2c", "linewidth")
			if linewidth then
				table.insert(argv, "-w")
				table.insert(argv, tostring(linewidth))
			end
			local nozeroend = target:extraconf("rules", "bin2c", "nozeroend")
			if nozeroend then
				table.insert(argv, "--nozeroend")
			end
			batchcmds:vrunv(os.programfile(), argv, {envs = {XMAKE_SKIP_HISTORY = "y"}})

			-- add deps
			batchcmds:add_depfiles(sourcefile_bin)
			batchcmds:set_depmtime(os.mtime(headerfile))
			batchcmds:set_depcache(target:dependfile(headerfile))

		end

		batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", outputHeader)
		io.writefile(outputHeader, outputHeaderContent)
		batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", outputSource)
		io.writefile(outputSource, outputSourceContent)
    end)