-- Debug&Run Page
function config_debug_run:on_dismissed()
	self._init_data = nil;
	self._new_config = nil;
	self._new_setting = nil;
	self._setting = nil;
	self._target = nil;
	self._config = nil;
	self._toolset = nil;
	self._init_target = nil;
end;

function config_debug_run:show_target(target)
	self._init_target = target;
	self:show();
end;

function config_debug_run:on_init()
	project:reload_settings();
	local toolsets = {};
	local targets = {};
	local all_toolsets = TOOLSETS;
	if project:is_esp_idf() then
		all_toolsets = ESP32_IDF_TOOLSETS;
	elseif project:is_bouffalo_sdk() then
		all_toolsets = BL_TOOLSETS;
	end;
	if all_toolsets then
		for idx, toolset in ipairs(all_toolsets) do
			table.insert(toolsets, toolset.name);
		end;
	end;
	local project_targets = project:targets();
	if project_targets then
		for idx, target in ipairs(project_targets) do
			if target.output ~= nil then
				table.insert(targets, target.name);
			end;
		end;
	end;
	local current_toolset = menu_bar.get_cmake_toolset(true);
	if current_toolset then
		current_toolset = current_toolset.name;
	else
		current_toolset = "";
	end;
	local init_data = {
		configs = {
			"Debug";
			"Release";
			"RelWithDebInfo";
			"MinSizeRel";
		};
		["toolsets"] = toolsets;
		["targets"] = targets;
		config = {
			[".targets"] = (project_configs and project_configs[".targets"]) or {};
			[".build"] = {
				target = self._init_target or (menu_bar.get_cmake_default_target() or {}).name;
				toolset = current_toolset;
				config = (menu_bar.get_cmake_config() or "");
			};
		}
	};
	self._init_data = init_data;
	self._new_config = {};	-- modified self._init_data.config[".targets"]
	self._new_setting = nil;	-- modified self._setting
	self._setting = nil;	-- setting for current target/config/toolset
	self:init(init_data);
end;

function config_debug_run:on_apply()
	if project_configs == nil then
		-- not a cmake project
		return false;
	end;

	if self._new_config == nil then
		return true;
	end;

	local config = self._init_data.config[".targets"];

	local function env_parser(val)
		val = val.."\n";
		local env = {};
		for key, val in val:gmatch("([^=%s]+)=?([^\r\n]*)[\r\n]+") do
			env[key] = val;
		end;
		return env;
	end;

	local function write_cfg(target, setting_type, cfg_name, cfg_value, parser)
		if parser == nil then
			parser = function(val) return val; end;
		end;
		if cfg_value == nil or cfg_value == "" then
			if config[target] and config[target][cfg_name] and config[target][cfg_name][setting_type] then
				config[target][cfg_name][setting_type] = nil;
			end;
		else
			if config[target] == nil then
				config[target] = {};
			end;
			if config[target][cfg_name] == nil then
				config[target][cfg_name] = {};
			elseif type(config[target][cfg_name]) == "string" then
				if config[target][cfg_name] == cfg_value then
					return;
				end;
				config[target][cfg_name] = {default = config[target][cfg_name]};
			end;
			if config[target][cfg_name]["default"] == cfg_value then
				config[target][cfg_name][setting_type] = nil;
			else
				config[target][cfg_name][setting_type] = parser(cfg_value);
			end;
		end;
	end;
	-- print("new==>")
	-- print_table(self._new_config);

	for target, settings in pairs(self._new_config) do
		for setting_type, setting in pairs(settings) do
			write_cfg(target, setting_type, "argument", setting.argument);
			write_cfg(target, setting_type, "cwd", setting.cwd);
			write_cfg(target, setting_type, "environment", setting.environment, env_parser);
		end;
		if sizeof(config[target].argument) == 0 then
			config[target].argument = nil;
		end;
		if sizeof(config[target].cwd) == 0 then
			config[target].cwd = nil;
		end;
		if sizeof(config[target].environment) == 0 then
			config[target].environment = nil;
		end;
		if sizeof(config[target]) == 0 then
			config[target] = nil;
		end;
	end;

	project_configs[".targets"] = config;
	-- print("merged==>");
	-- print_table(config);
	project:save_settings(true);
	return true;
end;

function config_debug_run:on_cancel()
	self._new_config = {};
	self:on_selection_changed(self._target, self._config, self._toolset);
end;

function config_debug_run:_get_new_setting()
	if self._new_setting == nil then
		self._new_setting = table.clone(self._setting);
		self._new_config[self._target] = self._new_config[self._target] or {};
		self._new_config[self._target][self._toolset.."-"..self._config] = self._new_setting;
	end;
	return self._new_setting;
end;

function config_debug_run:on_argument_changed()
	self:_get_new_setting().argument = self.argument;
end;

function config_debug_run:on_cwd_changed()
	self:_get_new_setting().cwd = self.cwd;
end;

function config_debug_run:on_environment_changed()
	self:_get_new_setting().environment = self.environment;
end;

function config_debug_run:on_ssh_upload_filter_changed()
	local init_data_targets = self._init_data.config[".targets"];
	if not init_data_targets[".ssh_upload_filter"] then
		init_data_targets[".ssh_upload_filter"] = {};
	end;
	init_data_targets[".ssh_upload_filter"][self._toolset] = self.ssh_upload_filter;
end;

function config_debug_run:update_setting()
	self.argument = self._setting.argument;
	self.cwd = self._setting.cwd;
	self.environment = self._setting.environment;
end;
	
function config_debug_run:on_selection_changed(target, config, toolset)
	self._new_setting = nil;
	self._setting = nil;
	self._target = target;
	self._config = config;
	self._toolset = toolset;
	local is_remote_toolset = false;
	for idx, tool in ipairs(TOOLSETS) do
		if tool.name == toolset and tool.remote then
			is_remote_toolset = true;
			break;
		end;
	end;
	self.show_ssh_upload_filter(is_remote_toolset);

	local init_data_targets = self._init_data.config[".targets"];
	if is_remote_toolset then
		self.ssh_upload_filter = (init_data_targets[".ssh_upload_filter"] and init_data_targets[".ssh_upload_filter"][toolset]) or "";
	end;

	local setting_name = toolset.."-"..config;
	local target_cfg = self._new_config and self._new_config[target] and self._new_config[target][setting_name];
	if target_cfg then
		self._setting = target_cfg;
		self:update_setting();
		return;
	end;

	target_cfg = init_data_targets[target];
	if target_cfg == nil then
		self._setting = {
			argument = "";
			cwd = "";
			environment = "";
		};
		self:update_setting();
		return;
	end;

	local function get_setting(settings, name, def_val)
		local val = settings[name];
		if type(val) == "string" then
			return val;
		end;
		if type(val) == "table" then
			return val[setting_name] or val["default"] or def_val;
		end;
		return def_val;
	end;

	local function make_env_string(env)
		if env == nil then
			return "";
		end;
		local keys = table.keys(env);
		table.sort(keys);
		local envs = {};
		for idx, key in ipairs(keys) do
			table.insert(envs, key .. "=" .. (env[key]));
		end;
		return table.concat(envs, "\r\n");
	end;

	self._setting = {
		argument = get_setting(target_cfg, "argument", "");
		cwd = get_setting(target_cfg, "cwd", "");
		environment = make_env_string(get_setting(target_cfg, "environment", nil));
	};
	self:update_setting();
end;

-- Credential Page
function credential_page:on_open_item(item)
	local info = {item:match("(ssh://[^/]+)(.*)")};
	if info[1] then
		local path = info[2];
		local host = info[1];

		local sshcon = sshcon_path;
		local cmd = sshcon.." -compress "..host.." \"$SHELL --login -i\"";
		edx:shell2(cmd, "*", "");
	end;
end;

-- ClangTidy Page
require "clang_tidy_config"

function clang_tidy_page:on_dismissed()
	if self.__updated and lsp then
		print("Reseting LSP...");
		local cmd = project:find_build_command(nil, true);
		lsp:reset(cmd);
	end;
	self.__updated = nil;
end;

function clang_tidy_page:on_init()
	self.__updated = nil;
	local config = table.clone(CLANG_TIDY_CONFIG);
	local checks = {};
	if project:is_ready() then
		-- is cmake project
		local clang_tidy = project:make_path(".clang-tidy");
		local prefix_lines = {};
		local postfix_lines = {};
		local is_prefix = true;
		if utils:file_time(clang_tidy) then
			local clang_tidy = io.u8_open(clang_tidy, "r");
			local in_check = false;
			local line_idx = 0;
			if clang_tidy then
				for line in clang_tidy:lines() do
					line_idx = line_idx + 1;
					if line:match("^%s*%-+%s*$") and line_idx == 1 then
						-- skip empty line
					elseif in_check then
						local val = line:match("^%s+(%S+)%s*$");
						if val then
							table.insert(checks, split_string(val, ","));
						else
							in_check = false;
							table.insert(postfix_lines, line);
						end;
					elseif not is_prefix then
						table.insert(postfix_lines, line);
					else
						local lm = {line:match("^(Checks)%s*:%s*(.*)$")};
						if lm[2] ~= "" and lm[2] ~= ">" then
							-- string mode
							line = lm[2]:gsub("\"(.*)\"", "%1");
							if line then
								checks = {split_string(line, ",")};
							end;
							is_prefix = false;
						elseif lm[1] then
							in_check = true;
							is_prefix = false;
						else
							table.insert(prefix_lines, line);
						end;
					end;
				end;
				clang_tidy:close();
			end;
		end;

		-- trim spaces
		for idx, val in ipairs(checks) do
			checks[idx] = val:gsub("%s*(%S*)%s*", "%1");
		end;
		self.__prefix_lines = prefix_lines;
		self.__postfix_lines = postfix_lines;
	end;
	config["checks"] = checks;
	self:init(config);
end;

function clang_tidy_page:on_apply()
	local checks = {self:get_checks()};
	if project:is_ready() then
		-- is cmake project
		for idx, val in ipairs(checks) do
			checks[idx] = "  "..val;
		end;
		if #checks > 0 then
			table.insert(checks, 1, "Checks:");
		end;
		local lines = table.iclone(self.__prefix_lines, checks, self.__postfix_lines, {""});
		local clang_tidy = project:make_path(".clang-tidy");
		local clang_tidy_data = table.concat(lines, "\n");
		write_file(clang_tidy, clang_tidy_data);
		self.__updated = true;
	end;
end;

function clang_tidy_page:on_cancel()
end;

function clang_tidy_page:on_find_detail(group, rule)
	local url = ([[https://clang.llvm.org/extra/clang-tidy/checks/%s/%s.html]]):format(group, rule);
	edx:shell_execute("open", url, "");
end;

-- MSYS2 Page
function msys2_page:on_create()
	if not project:is_cmake() or project:is_esp_idf() or project:is_bouffalo_sdk() then
		self:hide();
		return;
	end;
end;

function msys2_page:on_init()
	self._cmake_project_line = nil;
	self._cmake_include_line = nil;
	self._cmake_include_comment = nil;
	self._is_enabled = nil;
	pinvoke(function()
			local msys_pkg_file = project:make_path("msys-pkg.cmake");
			local has_msys_pkg_file = false;
			local has_include_msys_pkg = false;
			local current_mirror = "";
			if msys_pkg_file and utils:file_time(msys_pkg_file) then
				has_msys_pkg_file = true;
				local file = io.u8_open(msys_pkg_file, "r");
				for line in file:lines() do
					local url = string.match(line, [[%s*set%(_EDX_MSYS_HOST_URL%s+(http.*)%)%s*]]);
					if url then
						current_mirror = url;
						break;
					end;
				end;
				file:close();
			end;

			local cmake_file = io.u8_open(project:make_path("CMakeLists.txt"), "r");
			if cmake_file then
				local cmake_line = 0;
				for line in cmake_file:lines() do
					if string.match(line, [[^%s*project%(.*%)%s*]]) then
						self._cmake_project_line = cmake_line;
					elseif string.match(line, [[^%s*include%(msys%-pkg%.cmake%)%s*]]) then
						has_include_msys_pkg = true;
						self._cmake_include_line = cmake_line;
						break;
					elseif string.match(line, [[^%s*#%s*include%(msys%-pkg%.cmake%)%s*]]) then
						self._cmake_include_line = cmake_line;
						self._cmake_include_comment = true;
					end;
					cmake_line = cmake_line + 1;

				end;
				cmake_file:close();
			end;
			self._is_enabled = project:is_ready() and project:is_cmake() and has_include_msys_pkg and has_msys_pkg_file;
			local status = {
				["project-type"] = (project:is_cmake() and "cmake") or (project:is_xmake() and "xmake") or "";
				["enabled"] = self._is_enabled;
				["mirror"] = current_mirror or "https://mirror.msys2.org/mingw";
				["mirrors"] = {
					[[https://mirror.msys2.org/mingw]];
					[[https://repo.msys2.org/mingw]];
					[[https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw]];
					[[https://mirror.umd.edu/msys2/mingw]];
					[[https://mirror.yandex.ru/mirrors/msys2/mingw]];
					[[https://download.nus.edu.sg/mirror/msys2/mingw]];
					[[https://ftp.acc.umu.se/mirror/msys2.org/mingw]];
					[[https://ftp.nluug.nl/pub/os/windows/msys2/builds/mingw]];
					[[https://ftp.osuosl.org/pub/msys2/mingw]];
					[[https://mirror.internet.asn.au/pub/msys2/mingw]];
					[[https://mirror.selfnet.de/msys2/mingw]];
					[[https://mirror.ufro.cl/msys2/mingw]];
					[[https://mirrors.dotsrc.org/msys2/mingw]];
					[[https://mirrors.bfsu.edu.cn/msys2/mingw]];
					[[https://mirrors.ustc.edu.cn/msys2/mingw]];
					[[https://mirror.nju.edu.cn/msys2/mingw]];
					[[https://repo.extreme-ix.org/msys2/mingw]];
					[[https://mirrors.hit.edu.cn/msys2/mingw]];
					[[https://mirror.clarkson.edu/msys2/mingw]];
					[[https://quantum-mirror.hu/mirrors/pub/msys2/mingw]];
					[[https://mirror2.sandyriver.net/pub/software/msys2/mingw]];
					[[https://mirror.archlinux.tw/MSYS2/mingw]];
					[[https://fastmirror.pp.ua/msys2/mingw]];
					[[https://ftp.cc.uoc.gr/mirrors/msys2/mingw]];
					[[https://mirror.jmu.edu/pub/msys2/mingw]];
					[[https://mirrors.piconets.webwerks.in/msys2-mirror/mingw]];
					[[https://www2.futureware.at/~nickoe/msys2-mirror/mingw]];
					[[https://mirrors.sjtug.sjtu.edu.cn/msys2/mingw]];
					[[https://mirrors.bit.edu.cn/msys2/mingw]];
					[[https://repo.casualgamer.ca/mingw]];
					[[https://mirrors.aliyun.com/msys2/mingw]];
					[[https://mirror.iscas.ac.cn/msys2/mingw]];
					[[https://mirrors.tencent.com/msys2/mingw]];
				}
			};
			self:init(status);
	end);
end;

function msys2_page:on_dismissed()
end;

function msys2_page:on_apply()
	if not project:is_cmake() then
		return false;
	end;
	pinvoke(function()
			local status = {self:get_status()};
			if status[1] then
				-- enable support
				local msys_pkg_file = project:make_path("msys-pkg.cmake");
				if utils:file_time(msys_pkg_file) == nil then
					local msys_pkg_file_source = make_path(edx_home_path, "cmake/msys-pkg.cmake");
					if utils:file_time(msys_pkg_file_source) == nil then
						msys_pkg_file_source = edx.make_config_path("cmake/msys-pkg.cmake", CFG_PATH_CWD);
					end;
					utils:copy_file(msys_pkg_file_source, msys_pkg_file);
				end;
				local file = io.u8_open(msys_pkg_file, "r");
				local new_file_data = {};
				for line in file:lines() do
					local url = string.match(line, [[%s*set%(_EDX_MSYS_HOST_URL%s+(http.*)%)%s*]]);
					if url then
						table.insert(new_file_data, string.format([[	set(_EDX_MSYS_HOST_URL %s)]], status[2]));
					else
						table.insert(new_file_data, line);
					end;
				end;
				file:close();
				write_file(msys_pkg_file, table.concat(new_file_data, "\n"));
				if self._is_enabled then
					return;
				end;
				local cmake_file = project:make_path("CMakeLists.txt");
				mgr.open_doc(cmake_file);
				local doc = mgr.get_document(cmake_file);
				if self._cmake_include_line ~= nil then
					doc:set_cursor(self._cmake_include_line, 0);
					if self._cmake_include_comment then
						doc.select_text(doc.cursor_line, doc.cursor_column, doc.cursor_line, 0xFFFFFFFF);
						doc.replace("include(msys-pkg.cmake)", false);
						doc.save();
						self._cmake_include_comment = false;
					end;
				else
					local msys_pkg_info = {
						"";
						"cmake_policy(SET CMP0074 NEW)";
						"include(msys-pkg.cmake)";
						"##########################################################################";
						"# \x1b[34mmsys-pkg.cmake\x1b[33m Usage\x1b[0m:";
						"# \x1b[37m1. Find a package with '\x1b[34mmsys-\x1b[0m\x1b[37m' prefix";
						"# find_package(msys-zlib)";
						"#";
						"# \x1b[37m2. Link the package(\x1b[34mzlib\x1b[0m\x1b[37m) to \x1b[34m<TARGET>\x1b[0m";
						"# target_link_libraries(<TARGET> zlib)";
						"#";
						"# \x1b[37mAll available packages can be found in:";
						"# \t\x1b[33m" .. make_path(edx_appdata_path, "msys2-cache/mingw32/cmake") .. "\x1b[0m";
						"# \t\x1b[33m" .. make_path(edx_appdata_path, "msys2-cache/mingw64/cmake") .. "\x1b[0m";
						"##########################################################################";
						"";
					};
					if self._cmake_project_line ~= nil then
						doc.set_cursor(self._cmake_project_line, 0xFFFFFFFF);
						table.insert(msys_pkg_info, 1, "\n");
					else
						doc.set_cursor(self._cmake_project_line, 0);
						table.insert(msys_pkg_info, "\n");
					end;

					doc.replace(table.concat(msys_pkg_info, "\n"), false);
					doc.save();
					self._cmake_include_line = self._cmake_project_line + 2;
				end;
				self._is_enabled = true;
			elseif self._is_enabled then
				-- disable support
				local cmake_file = project:make_path("CMakeLists.txt");
				mgr.open_doc(cmake_file);
				local doc = mgr.get_document(cmake_file);
				if self._cmake_include_line ~= nil then
					doc.set_cursor(self._cmake_include_line, 0);
					if not self._cmake_include_comment then
						doc.replace("# ", false);
						self._cmake_include_comment = true;
						self._is_enabled = false;
						doc.save();
					end;
				end;
			end;
	end);
end;

-- CMake Page
function cmake_page:on_create()
	if not project:is_cmake() then
		self:hide();
		return;
	end;
end;
function cmake_page:on_init()
	self.__data = nil;
	self.__scope = nil;
	self.__config = nil;
	self.__toolset = nil;
	pinvoke(function()
			project:reload_settings();
			local toolsets = {};
			local all_toolsets = TOOLSETS;
			local toolset_suffix = "";
			if project:is_esp_idf() then
				all_toolsets = ESP32_IDF_TOOLSETS;
				toolset_suffix = "-"..menu_bar.get_cmake_cpu();
			elseif project:is_bouffalo_sdk() then
				all_toolsets = BL_TOOLSETS;
				toolset_suffix = "-"..menu_bar.get_cmake_cpu();
			end;
			if all_toolsets then
				for idx, toolset in ipairs(all_toolsets) do
					table.insert(toolsets, toolset.name);
				end;
			end;
			local current_toolset = menu_bar.get_cmake_toolset(true);
			if current_toolset then
				current_toolset = current_toolset.name;
			else
				current_toolset = "";
			end;
			local current_config = (menu_bar.get_cmake_config() or "");
			local scope = current_config.." - "..current_toolset..toolset_suffix;
			self.__scope = scope;
			self.__config = current_config;
			self.__toolset = current_toolset;
			local cmake_vars = pinvoke(self.load_scope_vars, self, current_config, current_toolset, toolset_suffix);
			local init_data = {
				["configs"] = {
					"Debug";
					"Release";
					"RelWithDebInfo";
					"MinSizeRel";
				};
				["toolsets"] = toolsets;
				["active"] = {
					toolset = current_toolset;
					config = current_config;
				};
				["vars"] = cmake_vars;
			};
			self.__data = {
				[scope] = cmake_vars;
			};
			self:init(init_data);
	end);
end;

local cmake_internal_variables = {
	["GIT_EXECUTABLE"] = 1;
	["LINT"] = 1;
};

local function is_cmake_internal_vars(vname, vtype)
	local prefix = vname:sub(1,6);
	if prefix == "CMAKE_" or prefix == "CPACK_" or prefix == "CTEST_" then
		return true;
	end;
	if vtype == "STATIC" then
		local s11_vname = vname:sub(#vname + 1 - 11, #vname);
		local s12_vname = vname:sub(#vname + 1 - 12, #vname);
		local s13_vname = vname:sub(#vname + 1 - 13, #vname);
		if s13_vname == "_IS_TOP_LEVEL" or
			s11_vname == "_BINARY_DIR" or
			s11_vname == "_SOURCE_DIR" or
			s12_vname == "_LIB_DEPENDS"
		then
			return true;
		end;
	end;
	return cmake_internal_variables[vname];
end;

function cmake_page:load_scope_vars(config, toolset, toolset_suffix)
	local cmake_vars = {};
	local cmd = project:find_build_command(nil, true);
	if toolset_suffix == nil then
		toolset_suffix = "";
	end;
	local cmake_cahce_file_path = make_path(cmd.build_path, "../../", toolset..toolset_suffix, config, "CMakeCache.txt");
	local cmake_cahce_file = io.u8_open(cmake_cahce_file_path);
	if cmake_cahce_file then
		local comment_text = {};
		for ln in cmake_cahce_file:lines() do
			if #ln == 0 then
				comment_text = {};
			elseif ln:sub(1,1) == "#" then
				if ln:match("^# INTERNAL cache entries") then
					break;
				end;
			elseif ln:sub(1,2) == "//" then
				table.insert(comment_text, ln:sub(3,#ln));
			else
				local vdesc = table.concat(comment_text, "\n");
				local vname,vtype,value = ln:match("^(.-):(.-)=(.+)$");
				if vname and vtype then
					cmake_vars[vname] = {
						["name"] = vname;
						["type"] = vtype;
						["cval"] = value;
						["desc"] = vdesc;
						["deleted"] = vtype == "UNINITIALIZED" or is_cmake_internal_vars(vname, vtype);
					};
				end;
			end;
		end;
		cmake_cahce_file:close();
	else
		show_notify(LANG("notify/notice/cmake_cache_not_found"));
	end;
	-- load exists user variables
	local cmake_setting = project_configs[".cmake"] or {};
	local scope = config.." - "..toolset..toolset_suffix;
	local args = (cmake_setting.arguments and (cmake_setting.arguments[scope] or {})) or cmake_setting.argument or {};
	for idx, info in ipairs(args) do
		local key, value = info:match("^-D(.-)=(.*)$");
		if key then
			local var_info = cmake_vars[key];
			if #value > 2 and value:sub(1,1) == [["]] and value:sub(#value, #value) then
				value = value:sub(2, #value-1);
				value = value:gsub([[\"]], [["]]);
			end;
			if var_info then
				var_info.value = value;
				var_info.deleted = nil;
			else
				cmake_vars[key] = {
					["name"] = key;
					["value"] = value;
				};
			end;
		end;
	end;
	-- print("CMAKE_VARS[", config, " - ", toolset, "]:");
	-- print_table(cmake_vars);
	return cmake_vars;
end;

function cmake_page:on_dismissed()
	self.__data = nil;
	self.__scope = nil;
	self.__config = nil;
	self.__toolset = nil;
end;

function cmake_page:on_apply()
	pinvoke(function()
			local cmake_setting = project_configs[".cmake"];
			if not cmake_setting then
				cmake_setting = {};
				project_configs[".cmake"] = cmake_setting;
			end;
			if not cmake_setting.arguments then
				cmake_setting.arguments = {};
			end;
			for scope, vars in pairs(self.__data) do
				local args = {};
				for key, info in pairs(vars) do
					if info.value and not info.deleted then
						local value = info.value;
						if value ~= [[""]] and value:match("[\t \"]+") then
							value = value:gsub([["]], [[\"]]);
							table.insert(args, string.format("-D%s=\"%s\"", key, value));
						else
							table.insert(args, string.format("-D%s=%s", key, value));
						end;
					end;
				end;
				cmake_setting.arguments[scope] = args;
			end;
			-- print("cmake settings:");
			-- print_table(cmake_setting);

			-- schedule the config command for the next build
			project:schedule_reconfig(true);
			project:save_settings(true);
	end);
	return true;
end;

function cmake_page:on_cancel()
	pinvoke(function()
			self.__data = {}; -- discard all changes
			local scope = self.__scope;
			local config = self.__config;
			local toolset = self.__toolset;
			-- reload all variables
			local toolset_suffix = "";
			if project:is_esp_idf() or project:is_bouffalo_sdk() then
				toolset_suffix = "-"..menu_bar.get_cmake_cpu();
			end;
			local cmake_vars = pinvoke(self.load_scope_vars, self, config, toolset, toolset_suffix);
			self.__data[scope] = cmake_vars;
			self:init({["vars"] = cmake_vars});
	end);
	return true;
end;

function cmake_page:on_scope_changed(config, toolset)
	if not config or not toolset then
		return;
	end;
	local toolset_suffix = "";
	if project:is_esp_idf() or project:is_bouffalo_sdk() then
		toolset_suffix = "-"..menu_bar.get_cmake_cpu();
	end;
	local scope = config.." - "..toolset..toolset_suffix;
	if scope == self.__scope then
		return;
	end;
	self.__scope = scope;
	self.__config = config;
	self.__toolset = toolset;
	if self.__data[scope] == nil then
		local cmake_vars = pinvoke(self.load_scope_vars, self, config, toolset, toolset_suffix);
		self.__data[scope] = cmake_vars;
		self:init({["vars"] = cmake_vars});
	else
		self:init({["vars"] = self.__data[scope]});
	end;
end;

function cmake_page:on_var_changed(key, value)
	local cmake_vars = self.__data[self.__scope];
	if not cmake_vars[key] then
		cmake_vars[key] = {["value"] = value; ["changed"] = true};
	else
		cmake_vars[key]["value"] = value;
		cmake_vars[key]["changed"] = true;
		cmake_vars[key]["deleted"] = nil;
	end;
end;

function cmake_page:on_var_removed(key)
	local cmake_vars = self.__data[self.__scope];
	if cmake_vars[key] then
		cmake_vars[key]["deleted"] = true;
	end;
end;
