function print_table(t, print_func, level)
	local print_cache = nil;
	if print_func == nil then
		print_cache = {};
		print_func = function(...)
			if #print_cache > 64 then
				print( table.concat(print_cache,"\n") );
				print_cache = {};
			end;
			table.insert(print_cache, table.concat({...}, ""));
		end;
	end;
	if level == nil then
		level = '';
	end;
	for name, val in pairs(t) do
		if type(val) == "table" then
			print_func( level .. name .. ' : {' );
			print_table( val, print_func, level .. "\t" );
			print_func( level .. '};' );
		else
			local common_formator = function(v) return "{"..type(v).."}";end;
			local type_formattor = {
				["userdata"] = common_formator;
				["function"] = common_formator;
				["boolean"] = function(v) if v then return "true"; end; return "false"; end;
			};
			if type_formattor[type(val)] then
				val = type_formattor[type(val)](val);
			end;
			print_func( level .. name .. ' : ' .. val .. ';' );
		end;
	end;
	if print_cache then
		print(table.concat(print_cache, "\n"));
	end;
end;

lua_ex.print_class_info = function(self, class_name, print_func)
	local print_cache = nil;
	if print_func == nil then
		print_cache = {};
		print_func = function(...) 
			if #print_cache > 64 then
				print( table.concat(print_cache,"\n") );
				print_cache = {};
			end;
			table.insert(print_cache, table.concat({...}, ""));
		end;
	end;
	local function print_member( info_table, level )
		if level == nil then
			level = '';
		end;
		for key, val in pairs(info_table) do
			local ret_type = val[1];
			local name = val[2];
			local param = val["param"];
			local desc = val["desc"];
			if desc ~= nil then
				print_func( level .. "// " .. key .. " " .. desc );
			else
				print_func( level .. "// " .. key );
			end;
			local decl_text = ret_type .. " " .. name .. "(";
			if param ~= nil then
				local with_desc = false;
				local params = {};
				local pnum = 0;
				for pk, pv in pairs(param) do
					pnum = pnum + 1;
					local pinfo;
					if type(pv) == "table" then
						local ptype = pv[1];
						local pname = pv["name"];
						local desc = pv["desc"];
						pinfo = ptype;
						if pname then 
							pinfo = pinfo .. " " .. pname;
						end;
						if desc then
							pinfo = { pinfo, desc };
							with_desc = true;
						else
							pinfo = { pinfo };
						end;
					else
						pinfo = { pv };
					end;
					params[ pk ] = pinfo;
				end;
				if with_desc then
					print_func( level .. decl_text );
					local count = 0;
					for pk, pv in pairs(params) do
						count = count + 1;
						local text = pv[1];
						if count < pnum then
							text = text .. ", ";
						end;
						if pv[2] then
							text = text .. "\t// " .. pv[2];
						end;
						print_func( level .. "\t" .. text );
					end;
					print_func( level .. ");" );
				else
					local count = 0;
					for pk, pv in pairs(params) do
						count = count + 1;
						local text = pv[1];
						if count < pnum then
							text = text .. ", ";
						end;
						decl_text = decl_text .. text;
					end;
					print_func( level .. decl_text .. ");" );
				end;
			else
				print_func( level .. decl_text .. ");" );
			end;
		end;
	end;

	local info = self:class_info( class_name );
	if  type(info) ~= "table" then
		print_func( "Class info for [" .. class_name .. "] not found!" );
		return false;
	end;
	print_func( "class " .. info["name"] .. " {" );
	if type(info["member"]) == "table" and (#info["member"]) > 0 then
		print_func( "\t///////////////" );
		print_func( "\t// methods" );
		print_func( "\t///////////////" );
		print_member( info["member"], "\t" );
	end;
	if type(info["event"]) == "table" and (#info["event"]) > 0 then
		print_func( "\t///////////////" );
		print_func( "\t// events" );
		print_func( "\t///////////////" );
		print_member( info["event"], "\t" );
	end;
	print_func( "};" );

	if print_cache then
		print(table.concat(print_cache, "\n"));
	end;
	return true;
end

function enum(enumerator)
	return function() 
		if enumerator.eof() then
			enumerator.destroy();
			enumerator = nil;
			return nil;
		else
			local name = enumerator.name;
			local value = enumerator.value;
			enumerator.next();
			return name, value;
		end;
	end;
end;

function cmp_ver(ver1, ver2)
	ver1 = {ver1:match("(%d+)%.(%d+)%.(%d+)")};
	ver2 = {ver2:match("(%d+)%.(%d+)%.(%d+)")};

	for idx=1,3 do
		local v1 = tonumber(ver1[idx]);
		local v2 = tonumber(ver2[idx]);
		if  v1 ~= v2 then
			return v1 - v2;
		end;
	end;
	return 0;
end;

function sizeof(obj)
	if type(obj) == "string" then
		return #obj;
	end;
	if type(obj) == "table" then
		local counter = 0;
		for idx, val in pairs(obj) do
			counter = counter+1;
		end;
		return counter;
	end;
	if obj == nil then
		return 0;
	end;
	error( "can't get size" );
end;

-- make relative path into absolute path
---@return string
function abs_path(in_path)
	local path, n = string.gsub(in_path, [[([/\][^/\]+[/\]%.%.)]], "", 1);
	if n > 0 then
		return abs_path(path);
	end
	local path, m = string.gsub(path, [[([/\]+)]], "\\");
	return path;
end

-- concat all input parts into the file path
function make_path(...)
	local path_list = { ... };
	for idx, path in ipairs(path_list) do
		local last_ch = string.sub(path, #path);
		local first_ch = string.sub(path, 1, 2);
		if last_ch == '\\' or last_ch == '/' then
			path = string.sub(path, 1, #path -1);
		end;
		if first_ch == '\\' or first_ch == '/' then
			path = string.sub(path, 2, #path);
		end;
		path_list[idx] = path;
	end;
	return abs_path(table.concat(path_list, "\\"));
end

-- extract file name from input path
function get_file_name(in_path)
	local path, n = string.gsub(in_path, [[^(%w:[/\])]], "");
	path, n = string.gsub(path, [[([^/\]+[/\])]], "");
	if n > 0 then
		return get_file_name(path);
	end
	return path;
end

-- extract file base name from input path
function get_base_name(in_path)
	local full_name = get_file_name(in_path);
	local basename, n = string.gsub(full_name, [[(%.[^.]+)$]], "");
	return basename;
end

-- extract file extra name from input path
function get_ext_name(in_path)
	local full_name = get_file_name(in_path);
	local basename, n = string.gsub(full_name, [[^(.+%.)]], "");
	return basename;
end

function is_file_in_path(file_path, folder)
	if #file_path >= #folder then
		return file_path:sub(1,#folder) == folder;
	end;
	return false;
end;

function find_first(lst, matcher)
	local m = matcher;
	if type(matcher) == "string" then
		local mlen = #matcher;
		m = function(v)
			return v:sub(0,mlen) == matcher;
		end;
	end;

	for i,v in ipairs(lst) do
		if m(v) then
			return v, i;
		end;
	end;
	return nil, nil;
end;

function find_all(lst, matcher)
	local m = matcher;
	if type(matcher) == "string" then
		local mlen = #matcher;
		m = function(v)
			return v:sub(0,mlen) == matcher;
		end;
	end;
	local result = {};
	local index = {};
	for i,v in ipairs(lst) do
		if m(v) then
			table.insert(result, v);
			table.insert(index, i);
		end;
	end;
	if #result then
		return result, index;
	end;
	return nil, nil;
end;

function find_reg_path(reg_path, matcher)
	local subkeys = utils:list_reg(reg_path, matcher);
	if subkeys == nil then
		return nil;
	end;
	local key = subkeys[1];
	if key == nil then
		return nil;
	end;
	local new_reg_path = make_path(reg_path, key);
	return new_reg_path;
end;

function find_all_reg_path(reg_path, matcher)
	local keys = utils:list_reg(reg_path, matcher);
	if keys == nil then
		return nil;
	end;
	local paths = {};
	for idx, key in ipairs(keys) do
		local new_reg_path = make_path(reg_path, key);
		table.insert(paths, new_reg_path);
	end;
	return paths;
end;

function find_reg_val(reg_path, matcher, val_path)
	local new_reg_path = find_reg_path(reg_path, matcher);
	if new_reg_path == nil then
		return nil;
	end;
	local suffix = "";
	if val_path:sub(-1) == "\\" then
		suffix = "\\";
	end;
	new_reg_path = make_path(new_reg_path, val_path)..suffix;
	return utils:read_reg(new_reg_path);
end;

function find_all_reg_val(reg_path, matcher, val_path, filter)
	local new_reg_path = find_all_reg_path(reg_path, matcher);
	if new_reg_path == nil then
		return nil;
	end;
	local values = {};
	local suffix = "";
	if val_path:sub(-1) == "\\" then
		suffix = "\\";
	end;
	if type(filter) ~= "function" then
		filter = function() return false; end;
	end;
	for idx, path in ipairs(new_reg_path) do
		if not filter(path) then
			path = make_path(path, val_path)..suffix;
			local val = utils:read_reg(path);
			if val then
				table.insert(values, val);
			end;
		end;
	end;
	if #values then
		return values;
	end;
	return nil;
end;

function split_string(str, sp)
	local match = function(i,ch,str) return sp==ch; end;
	if type(sp) == "function" then
		match = sp;
	end;
	---@type string[]
	local strs = {};
	local start = 1;
	for i=1,#str,1 do
		local ch = str:sub(i,i);
		if match(i,ch,str) then
			if i>start then
				table.insert(strs, str:sub(start,i-1));
			end;
			start = i+1;
		end;
	end;
	if start <= #str then
		table.insert(strs, str:sub(start));
	end;
	return table.unpack(strs);
end;

function io.u8_open(path, mode)
	return io.open(utils:convert_string(path, "utf8", "acp"), mode);
end;

function load_file(file_path)
	local data_file = io.u8_open(file_path, "r");
	local data = data_file:read("a");
	data_file:close();
	return data;
end;

function write_file(file_path, file_data)
	local data_file = io.u8_open(file_path, "w+");
	data_file:write(file_data);
	data_file:close();
end;

function load_json(file_path)
	return utils:fromjson(load_file(file_path));
end;

function write_json(file_path, data)
	local json_data = utils:tojson(data);
	write_file(file_path, json_data);
end;

table.clone = function(...)
	local new_tab = {};
	for i, tab in ipairs({...}) do
		for name, value in pairs(tab) do
			new_tab[name] = value;
		end;
	end;
	return new_tab;
end;

table.deep_clone = function(...)
	local new_tab = {};
	for i, tab in ipairs({...}) do
		for name, value in pairs(tab) do
			if type(value) == "table" then
				new_tab[name] = table.deep_clone(value);
			else
				new_tab[name] = value;
			end;
		end;
	end;
	return new_tab;
end;

table.iclone = function(...)
	local new_tab = {};
	local idx = 0;
	for i, tab in ipairs({...}) do
		for name, value in ipairs(tab) do
			idx = idx + 1;
			new_tab[idx] = value;
		end;
	end;
	return new_tab;
end;

if table.unpack == nil then
	-- fix luajit compatibility issue
	table.unpack = unpack;
end;

table.append = function(tab, ...)
	for idx, val in ipairs({...}) do
		table.insert(tab, val);
	end;
	return tab;
end;

table.unique = function(tab)
	local new_tab = {};
	local vals = {};
	for idx, val in ipairs(tab) do
		if not vals[val] then
			vals[val] = true;
			table.insert(new_tab, val);
		end;
	end;
	return new_tab;
end;

table.keys = function(tab)
	local keys = {};
	for key, val in pairs(tab) do
		table.insert(keys, key);
	end;
	return keys;
end;

table.values = function(tab)
	local values = {};
	for key, val in pairs(tab) do
		table.insert(values, val);
	end;
	return values;
end;

if not math.tointeger then
	math.tointeger = function( val )
		return math.floor(tonumber(val));
	end;
end;
