local function find_esp_idf_ver(root)
	local esp_sdk_type = "esp-idf";
	local esp_idf_path = make_path(root, "components/esp_common/include/esp_idf_version.h");
	if not utils:file_time(esp_idf_path) then
		esp_sdk_type = "esp8266";
		esp_idf_path = make_path(root, "components/esp8266/include/esp_idf_version.h");
	end;
	if utils:file_time(esp_idf_path) then
		-- esp version file found
		local version_content = load_file(esp_idf_path);
		local version_content = {version_content:match("define%s+ESP_IDF_VERSION_MAJOR%s+(%d+).-define%s+ESP_IDF_VERSION_MINOR%s+(%d+).-define%s+ESP_IDF_VERSION_PATCH%s+(%d+)")};
		if version_content[1] then
			table.insert(version_content, esp_sdk_type);
			return table.unpack(version_content);
		end;
	end;
end;

local esp_target_arch = {
	["xtensa"] = {
		define = "-D__xtensa__=1 -D__XTENSA__=1 -D__XTENSA_EL__=1";
		gdb_type = "xtensa-esp-elf-gdb";
	};
	["risv32"] = {
		define = "-D__riscv=1 -D__riscv_mul=1 -D__riscv_div=1 -D__riscv_muldiv=1 -D__riscv_xlen=32 -D__riscv_compressed=1 -D__riscv_cmodel_medlow=1 -D__riscv_float_abi_soft=1";
		gdb_type = "riscv32-esp-elf-gdb";
	};
};

local esp_target_map = {
	["esp32"] = table.clone(esp_target_arch["xtensa"]);
	["esp32c2"] = table.clone(esp_target_arch["risv32"]);
	["esp32c3"] = table.clone(esp_target_arch["risv32"]);
	["esp32c5"] = table.clone(esp_target_arch["risv32"]);
	["esp32c6"] = table.clone(esp_target_arch["risv32"]);
	["esp32h2"] = table.clone(esp_target_arch["risv32"]);
	["esp32p4"] = table.clone(esp_target_arch["risv32"]);
	["esp32c61"] = table.clone(esp_target_arch["risv32"]);
	["esp32s2"] = table.clone(esp_target_arch["xtensa"]);
	["esp32s3"] = table.clone(esp_target_arch["xtensa"]);
	["esp8266"] = table.clone(esp_target_arch["xtensa"]);
};

local function find_esp_idf_toolset(root, repo)
	local tool_env = nil;
	local bin = nil;
	local openocd_root = nil;
	local cmake_path = nil;
	local cmake_ver = nil;
	local ninja_path = nil;
	local idf_targets = nil;
	local idf_git_path = nil;
	local idf_python_path = nil;
	local esp_ver_major = 0;
	local esp_ver_minor = 0;
	local esp_ver_patch = 0;
	if root == nil or utils:file_time(root) == nil then
		return nil;
	end;
	esp_ver_major, esp_ver_minor, esp_ver_patch, esp_sdk_type = find_esp_idf_ver(root);
	if not esp_ver_major then
		return nil;
	end;
	
	-- ESP-IDF installed
	local tools_json = make_path(root, "tools/tools.json");
	if utils:file_time(tools_json) == nil then
		return nil;
	end;
	tools_json = load_json(tools_json);
	local esp_tools = tools_json.tools or {};
	tool_env = {};
	bin = {};
	local all_idf_targets = {};
	for idx, tool in ipairs(esp_tools) do
		local tool_root = make_path(repo, "tools", tool.name, tool.versions[1].name);
		if tool.export_paths then
			for idx, path in ipairs(tool.export_paths) do
				local tool_path = make_path(tool_root, table.concat(path, "/"));
				if utils:file_time(tool_path) then
					table.insert(bin, tool_path);
					if tool.name == "openocd-esp32" then
						openocd_root = tool_path;
					elseif tool.name == "cmake" then
						cmake_path = make_path(tool_path, "cmake.exe");
						if utils:file_time(cmake_path) then
							table.insert(CMAKE_PATHS, cmake_path);
							cmake_ver = tool.versions[1].name;
							if cmake_ver then
								cmake_ver = cmake_ver:gsub("^(%d+)%.(%d+)%..*", function(v1,v2) return tonumber(v1) + (tonumber(v2)/100);end);
								cmake_ver = tonumber(cmake_ver);
							end;
						else
							cmake_path = nil;
						end;
					elseif tool.name == "ninja" then
						ninja_path = make_path(tool_path, "ninja.exe");
						if utils:file_time(ninja_path) then
							table.insert(NINJA_PATHS, ninja_path);
						else
							ninja_path = nil;
						end;
					end;
					if string.match(tool.name, ".*%-esp%-elf%-gdb") then
						-- gdb found
						for idx, target in ipairs(tool.supported_targets) do
							if esp_target_map[target] == nil then
								print("Unsupported target ", target, "!");
							end;
							esp_target_map[target].gdb_root = make_path(tool_path, "..");
							esp_target_map[target].gdb_name = tool.name;
						end;
					elseif string.match(tool.name, "xtensa%-lx106.*%-elf$") then
						-- compiler found
						for idx, target in ipairs({"esp8266"}) do
							all_idf_targets[target] = true;
							if not esp_target_map[target].root then
								esp_target_map[target].root = make_path(tool_path, "..");
								esp_target_map[target].name = tool.name;
							end;
						end;
					elseif string.match(tool.name, "xtensa%-esp.*%-elf$") then
						-- compiler found
						for idx, target in ipairs(tool.supported_targets) do
							all_idf_targets[target] = true;
							if not esp_target_map[target].root then
								esp_target_map[target].root = make_path(tool_path, "..");
								esp_target_map[target].name = tool.name;
							end;
						end;
					elseif tool.name == "riscv32-esp-elf" then
						-- compiler found
						for idx, target in ipairs(tool.supported_targets) do
							all_idf_targets[target] = true;
							if not esp_target_map[target].root then
								esp_target_map[target].root = make_path(tool_path, "..");
								esp_target_map[target].name = tool.name;
							end;
						end;
					end;
				end;
			end;
		end;
		if tool.export_vars then
			for name, value in pairs(tool.export_vars) do
				tool_env[name] = value:gsub("%${(.-)}", {
						["TOOL_PATH"] = tool_root;
					});
			end;
		end;
	end;
	local python_env_formats = {
		["esp-idf"] = "python_env/idf%u.%u_py*";
		["esp8266"] = "python_env/rtos%u.%u_py*";
	};
	local python_name = utils:list_dir(make_path(repo, string.format(python_env_formats[esp_sdk_type], esp_ver_major, esp_ver_minor)));
	tool_env["IDF_PYTHON_ENV_PATH"] = make_path(repo, "python_env", python_name);
	table.insert(bin, 1, make_path(tool_env["IDF_PYTHON_ENV_PATH"], "Scripts"));
	tool_env["IDF_PATH"] = root;
	tool_env["OPENOCD_ROOT"] = openocd_root;
	tool_env["CCACHE_DIR"] = edx.make_config_path("ccache", CFG_PATH_LOCAL_APPDATA)
	idf_targets = {};
	for key, val in pairs(all_idf_targets) do
		table.insert(idf_targets, key);
	end;
	table.sort(idf_targets);

	do
		local idf_git_versions = utils:list_dir(make_path(repo,"tools/idf-git/*"), true) or {};
		idf_git_path = make_path(repo,"tools/idf-git", (idf_git_versions[#idf_git_versions] or ""), "cmd");
		if utils:file_time(idf_git_path) then
			table.insert(bin, 1, idf_git_path);
		else
			idf_git_path = nil;
		end;

		local idf_python_versions = utils:list_dir(make_path(repo,"tools/idf-python/*"), true) or {};
		idf_python_path = make_path(repo,"tools/idf-python", idf_python_versions[#idf_python_versions]);
		if not utils:file_time(idf_python_path) then
			idf_python_path = nil;
		end;
	end;
	
	if root and (not idf_targets or #idf_targets == 0) then
		local esp_install_file = {
			["esp-idf"] = "install.bat";
			["esp8266"] = "install.sh";
		};
		print("\x1b[33;41mESP-IDF was not installed! Please run "..make_path(root, esp_install_file[esp_sdk_type]).."\x1b[0m");
		return nil;
	end

	local debug_adapter = nil;
	if openocd_root then
		local openocd_bin = make_path(openocd_root, "openocd.exe");
		local openocd_scripts = tool_env["OPENOCD_SCRIPTS"];
		debug_adapter = toolset_make_debug_adapter(
			{"JLink(OpenOCD)", "CMSIS-DAP"},
			{
				["JLink(OpenOCD)"] = {
					["bin"] = openocd_bin;
					["scripts"] = openocd_scripts;
				};
				["CMSIS-DAP"] = {
					["bin"] = openocd_bin;
					["scripts"] = openocd_scripts;
				};
				["ESP_USB_JTAG"] = {
					["bin"] = openocd_bin;
					["scripts"] = openocd_scripts;
					["type"] = "openocd";
					["interface"] = "esp_usb_jtag";
					["transports"] = {};
					["speeds"] = {};
				};
				["ESP_USB_BRIDGE"] = {
					["bin"] = openocd_bin;
					["scripts"] = openocd_scripts;
					["type"] = "openocd";
					["interface"] = "esp_usb_bridge";
					["transports"] = {};
					["speeds"] = {};
				};
				["ESP32_DEVKITJ_V1"] = {
					["bin"] = openocd_bin;
					["scripts"] = openocd_scripts;
					["type"] = "openocd";
					["interface"] = "ftdi/esp32_devkitj_v1";
					["transports"] = {};
					["speeds"] = {};
				};
				["ESP32S2_KALUGA_V1"] = {
					["bin"] = openocd_bin;
					["scripts"] = openocd_scripts;
					["type"] = "openocd";
					["interface"] = "ftdi/esp32s2_kaluga_v1";
					["transports"] = {};
					["speeds"] = {};
				};
			}
		);
	end;
	local target_info = {};
	for idx, target in pairs(idf_targets) do
		local esp_target = esp_target_map[target];
		local fullname = esp_target["name"];
		local target_root = esp_target["root"];
		local target_gdb_type = esp_target["gdb_type"];
		local target_name = utils:list_dir(make_path(target_root, "lib/gcc/*"));
		local target_ver = utils:list_dir(make_path(target_root, "lib/gcc", target_name, "*"));
		local gdb_home = make_path(repo, "tools", target_gdb_type);
		local gdb_ver = utils:list_dir(make_path(gdb_home, "*"));
		local gdb_path = make_path(gdb_home, gdb_ver, target_gdb_type, "bin", fullname.."-gdb.exe");
		target_info[target] = {
			root = target_root;
			name = target_name;
			version = target_ver;
			define = esp_target["define"];
			gdb_path = gdb_path;
			include = {
				make_path(target_root, "include");
				make_path(target_root, "lib/gcc", target_name, target_ver, "include");
				make_path(target_root, "lib/gcc", target_name, target_ver, "include-fixed");
				make_path(target_root, target_name, "sys-include");
				make_path(target_root, target_name, "include");
				make_path(target_root, target_name, "include/c++", target_ver);
			};
		};
	end;
	local ep32_idf = {
		["type"] = "esp-idf";
		["sdk_type"] = esp_sdk_type;
		["name"] = string.format("%s-%u.%u.%u", esp_sdk_type, esp_ver_major, esp_ver_minor, esp_ver_patch);
		["version"] = string.format("%u.%u.%u", esp_ver_major, esp_ver_minor, esp_ver_patch);
		["version_number"] = esp_ver_major*0x100000 + esp_ver_minor*0x1000 + esp_ver_patch;
		["home"] = make_path(root);
		["repo"] = repo;
		["envs"] = tool_env;
		["bin"] = bin;
		["include"] = {};
		["lib"] = {};
		["targets"] = idf_targets;
		["tools"] = {
			["debugger"] = "openocd";
			["idf"] = "python " .. make_path(root, [[tools\idf.py]]);
			["esptool"] = "python " .. make_path(root, [[components\esptool_py\esptool\esptool.py]]);
			["espefuse"] = "python " .. make_path(root, [[components\esptool_py\esptool\espefuse.py]]);
			["otatool"] = "python " .. make_path(root, [[components\app_update\otatool.py]]);
			["parttool"] = "python " .. make_path(root, [[components\partition_table\parttool.py]]);
			["openocd"] = "openocd";
			["cmake"] = cmake_path;
			["cmake_ver"] = cmake_ver;
			["ninja"] = ninja_path;
			["git"] = idf_git_path;
			["python"] = idf_python_path;
			["vcpkg"] = "DISABLED";
		};
		["target_info"] = target_info;
		["query_driver"] = function(self, cmd)
			local toolset = cmd.toolset;
			local target = toolset.target;
			local target_info = self.target_info[target];
			local target_root = target_info.root;
			local gcc_file = utils:list_dir(make_path(target_root, "bin/*-gcc.exe"));
			return make_path(target_root, "bin", gcc_file);
		end;
		["debug_adapter"] = debug_adapter;
		encoding = "utf-8";
	};
	return ep32_idf;
end;

local function find_esp32_idf_sdks()
	local esp32_idf_sdks = {};
	local selected_sdk = nil;
	do
		local repo = os.getenv("IDF_TOOLS_PATH") or "";
		local esp_idf_json = repo and make_path(repo, "esp_idf.json");
		local idf_env_json = repo and make_path(repo, "idf-env.json");
		local esp32_idf_paths = {};
		function collect_idf_sdk(idf_env)
			for k,idf_info in pairs(idf_env.idfInstalled) do
				local idf_path = edx:regular_path(make_path(idf_info.path));
				if not esp32_idf_paths[idf_path] then
					esp32_idf_paths[idf_path] = idf_path;
					local toolset = pinvoke(find_esp_idf_toolset, idf_path, repo);
					if toolset then
						table.insert(esp32_idf_sdks, toolset);
						if k == idf_env.idfSelectedId then
							selected_sdk = toolset;
						end;
					end;
				end;
			end;
		end;
		if utils:file_time(esp_idf_json) then
			local idf_env = load_json(esp_idf_json) or {idfInstalled={}};
			if idf_env.idfInstalled then
				collect_idf_sdk(idf_env)
			end;
		end;
		if utils:file_time(idf_env_json) then
			local idf_env = load_json(idf_env_json) or {idfInstalled={}};
			if idf_env.idfInstalled then
				collect_idf_sdk(idf_env)
			end;
		end;

		if repo then
			local esp8266_rtos_sdk = make_path(repo, "frameworks/ESP8266_RTOS_SDK");
			if utils:file_time(esp8266_rtos_sdk) then
				local toolset = pinvoke(find_esp_idf_toolset, esp8266_rtos_sdk:gsub("^(%w:)", function(v) return v:upper();end):gsub("\\", "/"), repo);
				table.insert(esp32_idf_sdks, toolset);
			end;
		end;
	end;

	do
		local repo = make_path(os.getenv("USERPROFILE") or "", ".espressif");
		local idf_env_json = make_path(repo, "idf-env.json");
		if utils:file_time(idf_env_json) then
			local idf_env = load_json(idf_env_json) or {idfInstalled={}};
			if idf_env.idfInstalled then
				for k,idf_info in pairs(idf_env.idfInstalled) do
					local toolset = pinvoke(find_esp_idf_toolset, idf_info.path, repo);
					if toolset then
						table.insert(esp32_idf_sdks, toolset);
						if selected_sdk == nil and k == idf_env.idfSelectedId then
							selected_sdk = toolset;
						end;
					end;
				end;
			end;
		end;
		local root = os.getenv("IDF_PATH");
		local toolset = root and pinvoke(find_esp_idf_toolset, root, repo);
		if toolset then
			table.insert(ESP32_IDF_TOOLSETS, toolset);
		end;
	end;
	if #esp32_idf_sdks == 0 then
		return nil;
	end;
	table.sort(esp32_idf_sdks, function(a,b) return a.version_number > b.version_number; end);
	selected_sdk = selected_sdk or esp32_idf_sdks[1];

	print("Found ESP-IDF(s)!");
	for idx, info in ipairs(esp32_idf_sdks) do
		if info == selected_sdk then
			print("\t\x1b[33;41m", info.version, "\x1b[0m => ", info.home, " support [", table.concat(info.targets or {}, ","), "]");
		else
			print("\t\x1b[34m", info.version, "\x1b[0m => ", info.home, " support [", table.concat(info.targets or {}, ","), "]");
		end;
	end;
	esp32_idf_def_toolset = selected_sdk and selected_sdk.name;
	return esp32_idf_sdks, selected_sdk;
end;

local function find_esp32_idf(root)
	local repo = make_path(os.getenv("USERPROFILE"), ".espressif");
	if not repo or not root then
		return nil;
	end;
	local toolset = pinvoke(find_esp_idf_toolset, root, repo);
	if toolset then
		table.insert(ESP32_IDF_TOOLSETS, toolset);
		ESP32_IDF_DEF_TOOLSET = toolset;
		ESP32_IDF_TOOLSET = ESP32_IDF_DEF_TOOLSET;
	end;
	return toolset;
end;

local ESP32_IDF_DEF_TOOLSET = nil;
ESP32_IDF_TOOLSETS, ESP32_IDF_DEF_TOOLSET = find_esp32_idf_sdks();
local ESP32_IDF_TOOLSET = ESP32_IDF_DEF_TOOLSET;
ESP32_IDF_CMAKE_PROJECT = false;

function esp_idf_create_project(path, name, target, on_finished_handler)
	if #ESP32_IDF_TOOLSETS == 0 then
		return false;
	end;
	local toolset = ESP32_IDF_DEF_TOOLSET;
	if not (toolset and toolset.target_info and toolset.target_info[target]) then
		-- the toolset does not support the target type
		for idx, info in ipairs(ESP32_IDF_TOOLSETS) do
			if info.target_info[target] then
				-- find a valid toolset
				toolset = info;
				break;
			end;
		end;
	end;
	
	local project_name = name:gsub("%s", "-");
	if toolset.sdk_type == "esp8266" then
		local make_file=[[PROJECT_NAME := %s
include $(IDF_PATH)/make/project.mk
]];

local cmake_list = [[
cmake_minimum_required(VERSION 3.13)

# set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/components)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(%s)
]];

local sub_cmake_list = [[
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
# component_compile_options(-O2 -s)
]];

local main_c = [[
#include <stdio.h>

void app_main(void)
{
	puts("hello world!");
}
]];
		if not utils:mkdir(make_path(path, "main")) then
			if on_finished_handler then
				on_finished_handler(false);
			end;
		end;
		write_file(make_path(path, "CMakeLists.txt"), cmake_list:format(project_name));
		write_file(make_path(path, "Makefile"), make_file:format(project_name));
		write_file(make_path(path, "main/CMakeLists.txt"), sub_cmake_list);
		write_file(make_path(path, "main/main.c"), main_c);
		if on_finished_handler then
			on_finished_handler(true, toolset.name, "main/main.c");
		end;
		return;
	end;

	local cmds = {[[cmd /d /c ]]..toolset.tools.idf..[[ create-project -p "]]..path..[[" ]] .. project_name};
	if target ~= "esp32" then
		table.insert(cmds,
			[[cmd /d /c ]]..toolset.tools.idf..[[ set-target -C "]]..path..[[" ]] .. target
		);
	end;
	local tasks = {
		desc = "ESP-IDF create project";
		envs = table.clone(toolset.envs, {
				PATH = table.concat(toolset.bin, ";")..";"..os.getenv("PATH");
		});
		["cmds"] = cmds;
		on_output = function(cmd_idx, output_line) end;
		on_command_begin = function(cmd_idx)
			print(cmds[cmd_idx]);
		end;
		on_command_end = function(cmd_idx, ret_code)
		end;
		on_finished = function(success)
			if on_finished_handler then
				on_finished_handler(success, toolset.name, "main/"..project_name..".c");
			end;
		end;
		encoding = "ansi";
	};
	edx:active("output");
	edx:do_build(tasks)
end;

function esp32_idf_set_target(target, config, callback)
	if not target then
		target = "esp32";
	end;
	local sdkconfig = project:make_path("sdkconfig");
	local sdkconfig_old = project:make_path("sdkconfig.old");
	if utils:file_time(sdkconfig_old) then
		os.remove(sdkconfig_old);
	end;

	if utils:file_time(sdkconfig) then
		os.rename(sdkconfig, sdkconfig..".old");
	end;
	local cmd = project:find_build_command(nil, true);
	local do_build_params = project:gen_do_build_params(cmd, nil, callback);
	do_build_params.desc = "ESP-IDF set-target";
	do_build_params.encoding = "ansi";
	edx:do_build(do_build_params)
end;

function esp32_idf_show_serial_monitor()
	local toolset = ESP32_IDF_TOOLSET;
	local cmd_line = [[cmd /d /k ]]..toolset.tools.idf..[[ monitor"]];
	local params = table.clone( toolset.envs, {
			PATH = table.concat(toolset.bin, ";")..";"..os.getenv("PATH");
	});
	params[1] = project:home();
	edx.shell2(cmd_line, "*", params);
end;

local function esp32_idf_show_command_line(path)
	local toolset = ESP32_IDF_TOOLSET;
	if not path then
		path = project:home();
	end;
	if toolset.sdk_type == "esp8266" then
		local cmd_line = [[cmd /d /k]];
		local params = table.clone( toolset.envs, {
				PATH = table.concat(toolset.bin, ";")..";"..os.getenv("PATH");
		});
		params[1] = path;
		edx.shell2(cmd_line, "*", params);
	else
		local cmd_line = [[cmd /d /k ]].. make_path(toolset.home, "export.bat");
		edx.shell2(cmd_line, "*", path);
	end;
end;

local old_project_target_filter = project._cmake_project_target_filter;
project._cmake_project_target_filter = function(self, target, cmd)
	if ESP32_IDF_CMAKE_PROJECT and cmd.toolset and cmd.toolset["type"] == "esp-idf" then
		return target["dir"] and target["dir"]:sub(1,8) == "esp-idf/";
	end;
	return old_project_target_filter(self, target, cmd);
end;

local old_project_filter = project._cmake_project_filter;
project._cmake_project_filter = function(self, targets, cmd)
	targets = old_project_filter(self, targets, cmd);
	if ESP32_IDF_CMAKE_PROJECT then
		local idf_build_path = make_path(cmd.build_path, "esp-idf");
		local esp_targets = {};
		for idx, target in ipairs(targets) do
			if target["type"] ~= 4 and not is_file_in_path(make_path(target.output), idf_build_path) then
				table.insert(esp_targets, target);
			end;
		end;
		-- add install target enable the install menu item
		table.insert(esp_targets, {
				["name"] = "install";
				["type"] = 4;
		});
		-- enable flash target
		table.insert(esp_targets, {
				["name"] = "flash";
				["type"] = 4;
		});
		return esp_targets;
	end;
	return targets;
end;
local old_cmake_find_build_command = project._cmake_find_build_command;
project._cmake_find_build_command = function(self, build_target)
	if not ESP32_IDF_CMAKE_PROJECT then
		return old_cmake_find_build_command(self, build_target);
	end;
	-- is ESP-IDF project
	local config = menu_bar.get_cmake_config();
	local toolset = nil;
	local target = menu_bar.get_cmake_cpu() or "esp32";
	local cmake_extra_arguments = nil;
	local esp_toolset = ESP32_IDF_TOOLSET;
	local target_info = esp_toolset.target_info[target];
	toolset = table.clone(esp_toolset, {
			["name"] = esp_toolset.name.."-"..target;
			["target"] = target;
			["include"] = target_info.include;
			["define"] = target_info.define;
			["envs"] = table.clone(esp_toolset.envs, {
					["ESPPORT"] = menu_bar.get_cmake_serial_port() or (({utils:list_serial_ports()})[1] or {})[1];
					["ESPBAUD"] = menu_bar.get_cmake_serial_baud();
			});
			["tools"] = table.clone(esp_toolset.tools, {
					["gdb"] = target_info.gdb_path;
					["gcc-pretty-print"] = make_path(target_info.root, [[share/gcc-]]..target_info.version, [[python]]);
			});
	});
	if build_target == "install" then
		build_target = "flash";
	end;
	cmake_extra_arguments = " -DIDF_TARGET=\""..target.. "\" -DCCACHE_ENABLE=1";
	local build_cmd = self:_cmake_find_build_command_impl(build_target, toolset, config, cmake_extra_arguments);
	if build_cmd == nil then
		return nil;
	end;
	-- print_table(build_cmd);
	build_cmd.__esp_build_target = build_target;
	return build_cmd;
end;

local old_cmake_gen_do_build_params = project._cmake_gen_do_build_params;
project._cmake_gen_do_build_params = function(self, cmd, build_type, on_complete, prepend_cmds, apppend_cmds)
	if cmd.__esp_build_target == "flash" then
		local com_port = cmd.toolset.envs["ESPPORT"];
		-- if the corresponding port was opened, trying to stop the serial port monitor
		if edx:set_serial_monitor(com_port, false) then
			-- the com port is closed successfully
			local old_on_complete = on_complete;
			on_complete = function(success)
				-- trying to reopen the com port
				edx:set_serial_monitor(com_port, true);
				if old_on_complete then
					old_on_complete(success);
				end;
			end;
		end;
		build_type = "flash";
	end;
	return old_cmake_gen_do_build_params(self, cmd, build_type, on_complete, prepend_cmds, apppend_cmds);
end;

table.insert(project._toolset_changed_listeners,
	function(toolset)
		if ESP32_IDF_CMAKE_PROJECT then
			ESP32_IDF_TOOLSET = toolset;
			menu_bar.update_cmake_cpu(toolset.targets, menu_bar.get_cmake_cpu());
		end;
	end
);

local function esp32_idf_find_current_target(dir)
	local target = "esp32";
	local sdkconfig = make_path(dir, "sdkconfig");
	local sdkfile = io.u8_open(sdkconfig);
	if sdkfile then
		for ln in sdkfile:lines() do
			local m = string.match(ln, [[^CONFIG_IDF_TARGET=%"(.-)%"]]);
			if m then
				target = m;
				break;
			end;
		end;
		sdkfile:close();
	end;
	return target;
end;

table.insert(xws_mgr._pre_load_cmake_project,
	function(self, dir, cmake_lists, cmake_lists_data)
		ESP32_IDF_CMAKE_PROJECT = string.match(cmake_lists_data, [[include%(%$ENV{IDF_PATH}]]) ~= nil;
		if not ESP32_IDF_CMAKE_PROJECT then
			return false;
		end;
		if ESP32_IDF_TOOLSET == nil then
			-- trying find esp-idf in parent folders
			local root = make_path(dir, "..");
			while root and #root > 3 do
				local major_ver = find_esp_idf_ver(root);
				if major_ver ~= nil then
					ESP32_IDF_TOOLSET = find_esp32_idf(root);
					break;
				end;
				local name = utils:list_dir(make_path(root, "esp-idf*"));
				if name then
					name = make_path(root, name);
					local major_ver = find_esp_idf_ver(name);
					if major_ver ~= nil then
						ESP32_IDF_TOOLSET = find_esp32_idf(name);
						break;
					end;
				end;
				root = make_path(root, "..");
			end;
			if ESP32_IDF_TOOLSET == nil then
				-- esp-idf still not found
				ESP32_IDF_CMAKE_PROJECT = false;
				print("\x1b[33;41m", LANG("ESP-IDF not found!"),"\x1b[0m");
				print("\x1b[33;41m", LANG("Download from https://dl.espressif.cn/dl/esp-idf"),"\x1b[0m");
				return;
			end;
		end;
		local esp_toolset = ESP32_IDF_DEF_TOOLSET;
		local def_target = esp32_idf_find_current_target(dir);
		if project_configs == nil then
			project:reload_settings(dir);
		end;
		local def_buadrate = project:get_extra_setting("com_baud", SERIAL_DEFAULT_BAUDRATE);
		local mon_buadrate = project:get_extra_setting("mon_baud", SERIAL_DEFAULT_BAUDRATE);
		if esp_toolset.target_info[def_target] == nil then
			-- target was not supported by default toolset
			for idx, toolset in ipairs(ESP32_IDF_TOOLSETS) do
				if toolset.target_info[def_target] then
					esp_toolset = toolset;
					break;
				end;
			end;
		end;
		project.show_command_line = esp32_idf_show_command_line;
		menu_bar.update_cmake_toolset(ESP32_IDF_TOOLSETS);
		menu_bar.set_cmake_toolset(esp_toolset.name);
		menu_bar.show_cmake_items(
			"toolset",
			"serial_ports",
			"serial_baud",
			"cpus"
			-- "boards"
		);
		menu_bar.set_cmake_text(LANG("menu_bar/cmake/esp_idf"));
		menu_bar.update_cmake_cpu(esp_toolset.targets);
		menu_bar.set_cmake_cpu(def_target);
		menu_bar.update_cmake_serial_baud(
			SERIAL_BAUDRATES_HI,
			def_buadrate
		);
		menu_bar.update_cmake_serial_port(
			function()
				local ports = {utils:list_serial_ports()};
				table.sort(ports, function(a, b)
						return tonumber(a[1]:sub(4)) < tonumber(b[1]:sub(4));
				end);
				return ports;
			end,
			nil,
			function(val) return val[1], table.concat(val, "\t"); end
		);
		edx:update_serial_baudrate(SERIAL_BAUDRATES, mon_buadrate);
		return true;
	end
);

table.insert(xws_mgr._pre_unload_dir,
	function(self, dir)
		if not ESP32_IDF_CMAKE_PROJECT then
			return false;
		end;
		return false;
	end
);

table.insert(xws_mgr._post_unload_dir,
	function(self, dir)
		if ESP32_IDF_CMAKE_PROJECT then
			menu_bar.hide_cmake_items("serial_ports", "serial_baud", "debug_adapters", "debug_adapter_speed", "debug_adapter_transport", "cpus", "boards");
			menu_bar.show_cmake_items("toolset");
			menu_bar.update_cmake_toolset(TOOLSETS);
			menu_bar.set_cmake_text(LANG("menu_bar/cmake"));
			project.show_command_line = nil;
		end;
		ESP32_IDF_CMAKE_PROJECT = false;
		return false;
	end
);
-- esp32_idf_create_project(ESP32_IDF_TOOLSET, [[d:\projects\test-esp32]], [[test-esp32]])
