--[[ Methods ]]
--
re_m.test = check_re('RegEx', 'test', function(self, str, init)
	return re_rawfind(self.token, to_str_arr(str, init), 1, self.flags, self.verb_flags, true);
end);

re_m.match = check_re('RegEx', 'match', function(self, str, init, source)
	local span = re_rawfind(self.token, to_str_arr(str, init), 1, self.flags, self.verb_flags, false);
	if not span then
		return nil;
	end;
	return new_match(span, self.group_id, source, str);
end);

re_m.matchall = check_re('RegEx', 'matchall', function(self, str, init, source)
	str = to_str_arr(str, init);
	local i = 1;
	return function()
		local span = i <= str.n + 1 and re_rawfind(self.token, str, i, self.flags, self.verb_flags, false);
		if not span then
			return nil;
		end;
		i = span[0][2] + (span[0][1] >= span[0][2] and 1 or 0);
		return new_match(span, self.group_id, source, str.s);
	end;
end);

local function insert_tokenized_sub(repl_r, str, span, tkn)
	for _, v in ipairs(tkn) do
		if type(v) == "table" then
			if v[1] == "condition" then
				if span[v[2]] then
					if v[3] then
						insert_tokenized_sub(repl_r, str, span, v[3]);
					else
						table.move(str, span[v[2]][1], span[v[2]][2] - 1, #repl_r + 1, repl_r);
					end;
				elseif v[4] then
					insert_tokenized_sub(repl_r, str, span, v[4]);
				end;
			else
				table.move(v, 1, #v, #repl_r + 1, repl_r);
			end;
		elseif span[v] then
			table.move(str, span[v][1], span[v][2] - 1, #repl_r + 1, repl_r);
		end;
	end;
	repl_r.n = #repl_r;
	return repl_r;
end;

re_m.sub = check_re('RegEx', 'sub', function(self, repl, str, n, repl_flag_str, source)
	if repl_flag_str ~= nil and type(repl_flag_str) ~= "number" and type(repl_flag_str) ~= "string" then
		error(string.format("invalid argument #5 to 'sub' (string expected, got %s)", typeof(repl_flag_str)), 3);
	end
	local repl_flags = {
		l = false, o = false, u = false,
	};
	for f in string.gmatch(repl_flag_str or '', utf8.charpattern) do
		if repl_flags[f] ~= false then
			error("invalid regular expression substitution flag " .. f, 3);
		end;
		repl_flags[f] = true;
	end;
	local repl_type = type(repl);
	if repl_type == "number" then
		repl ..= '';
	elseif repl_type ~= "string" and repl_type ~= "function" and (not repl_flags.o or repl_type ~= "table") then
		error(string.format("invalid argument #2 to 'sub' (string/function%s expected, got %s)", repl_flags.o and "/table" or '', typeof(repl)), 3);
	end;
	if tonumber(n) then
		n = tonumber(n);
		if n <= -1 or n ~= n then
			n = math.huge;
		end;
	elseif n ~= nil then
		error(string.format("invalid argument #4 to 'sub' (number expected, got %s)", typeof(n)), 3);
	else
		n = math.huge;
	end;
	if n < 1 then
		return str, 0;
	end;
	local min_repl_n = 0;
	if repl_type == "string" then
		repl = to_str_arr(repl);
		if not repl_flags.l then
			local i1 = 0;
			local repl_r = table.create(3);
			local group_n = self.token.group_n;
			local conditional_c = { };
			while i1 < repl.n do
				local i2 = i1;
				repeat
					i2 += 1;
				until not repl[i2] or repl[i2] == 0x24 or repl[i2] == 0x5C or (repl[i2] == 0x3A or repl[i2] == 0x7D) and conditional_c[1];
				min_repl_n += i2 - i1 - 1;
				if i2 - i1 > 1 then
					table.insert(repl_r, table.move(repl, i1 + 1, i2 - 1, 1, table.create(i2 - i1 - 1)));
				end;
				if repl[i2] == 0x3A then
					local current_conditional_c = conditional_c[1];
					if current_conditional_c[2] then
						error("malformed substitution pattern", 3);
					end;
					current_conditional_c[2] = table.move(repl_r, current_conditional_c[3], #repl_r, 1, table.create(#repl_r + 1 - current_conditional_c[3]));
					for i3 = #repl_r, current_conditional_c[3], -1 do
						repl_r[i3] = nil;
					end;
				elseif repl[i2] == 0x7D then
					local current_conditional_c = table.remove(conditional_c, 1);
					local second_c = table.move(repl_r, current_conditional_c[3], #repl_r, 1, table.create(#repl_r + 1 - current_conditional_c[3]));
					for i3 = #repl_r, current_conditional_c[3], -1 do
						repl_r[i3] = nil;
					end;
					table.insert(repl_r, { "condition", current_conditional_c[1], current_conditional_c[2] ~= true and (current_conditional_c[2] or second_c), current_conditional_c[2] and second_c });
				elseif repl[i2] then
					i2 += 1;
					local subst_c = repl[i2];
					if not subst_c then
						if repl[i2 - 1] == 0x5C then
							error("replacement string must not end with a trailing backslash", 3);
						end;
						local prev_repl_f = repl_r[#repl_r];
						if type(prev_repl_f) == "table" then
							table.insert(prev_repl_f, repl[i2 - 1]);
						else
							table.insert(repl_r, { repl[i2 - 1] });
						end;
					elseif subst_c == 0x5C and repl[i2 - 1] == 0x24 then
						local prev_repl_f = repl_r[#repl_r];
						if type(prev_repl_f) == "table" then
							table.insert(prev_repl_f, 0x24);
						else
							table.insert(repl_r, { 0x24 });
						end;
						i2 -= 1;
						min_repl_n += 1;
					elseif subst_c == 0x30 then
						table.insert(repl_r, 0);
					elseif subst_c > 0x30 and subst_c <= 0x39 then
						local start_i2 = i2;
						local group_i = subst_c - 0x30;
						while repl[i2 + 1] and repl[i2 + 1] >= 0x30 and repl[i2 + 1] <= 0x39 do
							group_i ..= repl[i2 + 1] - 0x30;
							i2 += 1;
						end;
						group_i = tonumber(group_i);
						if not repl_flags.u and group_i > group_n then
							error("reference to non-existent subpattern", 3);
						end;
						table.insert(repl_r, group_i);
					elseif subst_c == 0x7B and repl[i2 - 1] == 0x24 then
						i2 += 1;
						local start_i2 = i2;
						while repl[i2] and
							(repl[i2] >= 0x30 and repl[i2] <= 0x39
								or repl[i2] >= 0x41 and repl[i2] <= 0x5A
								or repl[i2] >= 0x61 and repl[i2] <= 0x7A
								or repl[i2] == 0x5F) do
							i2 += 1;
						end;
						if (repl[i2] == 0x7D or repl[i2] == 0x3A and (repl[i2 + 1] == 0x2B or repl[i2 + 1] == 0x2D)) and i2 ~= start_i2 then
							local group_k = utf8_sub(repl.s, start_i2, i2);
							if repl[start_i2] >= 0x30 and repl[start_i2] <= 0x39 then
								group_k = tonumber(group_k);
								if not repl_flags.u and group_k > group_n then
									error("reference to non-existent subpattern", 3);
								end;
							else
								group_k = self.group_id[group_k];
								if not repl_flags.u and (not group_k or group_k > group_n) then
									error("reference to non-existent subpattern", 3);
								end;
							end;
							if repl[i2] == 0x3A then
								i2 += 1;
								table.insert(conditional_c, { group_k, repl[i2] == 0x2D, #repl_r + 1 });
							else
								table.insert(repl_r, group_k);
							end;
						else
							error("malformed substitution pattern", 3);
						end;
					else
						local c_escape_char;
						if repl[i2 - 1] == 0x24 then
							if subst_c ~= 0x24 then
								local prev_repl_f = repl_r[#repl_r];
								if type(prev_repl_f) == "table" then
									table.insert(prev_repl_f, 0x24);
								else
									table.insert(repl_r, { 0x24 });
								end;
							end;
						else
							c_escape_char = escape_chars[repl[i2]];
							if type(c_escape_char) ~= "number" then
								c_escape_char = nil;
							end;
						end;
						local prev_repl_f = repl_r[#repl_r];
						if type(prev_repl_f) == "table" then
							table.insert(prev_repl_f, c_escape_char or repl[i2]);
						else
							table.insert(repl_r, { c_escape_char or repl[i2] });
						end;
						min_repl_n += 1;
					end;
				end;
				i1 = i2;
			end;
			if conditional_c[1] then
				error("malformed substitution pattern", 3);
			end;
			if not repl_r[2] and type(repl_r[1]) == "table" and repl_r[1][1] ~= "condition" then
				repl, repl.n = repl_r[1], #repl_r[1];
			else
				repl, repl_type = repl_r, "subst_string";
			end;
		end;
	end;
	str = to_str_arr(str);
	local incr, i0, count = 0, 1, 0;
	while i0 <= str.n + incr + 1 do
		local span = re_rawfind(self.token, str, i0, self.flags, self.verb_flags, false);
		if not span then
			break;
		end;
		local repl_r;
		if repl_type == "string" then
			repl_r = repl;
		elseif repl_type == "subst_string" then
			repl_r = insert_tokenized_sub(table.create(min_repl_n), str, span, repl);
		else
			local re_match;
			local repl_c;
			if repl_type == "table" then
				re_match = utf8_sub(str.s, span[0][1], span[0][2]);
				repl_c = repl[re_match];
			else
				re_match = new_match(span, self.group_id, source, str.s);
				repl_c = repl(re_match);
			end;
			if repl_c == re_match or repl_flags.o and not repl_c then
				local repl_n = span[0][2] - span[0][1];
				repl_r = table.move(str, span[0][1], span[0][2] - 1, 1, table.create(repl_n));
				repl_r.n = repl_n;
			elseif type(repl_c) == "string" then
				repl_r = to_str_arr(repl_c);
			elseif type(repl_c) == "number" then
				repl_r = to_str_arr(repl_c .. '');
			elseif repl_flags.o then
				error(string.format("invalid replacement value (a %s)", type(repl_c)), 3);
			else
				repl_r = { n = 0 };
			end;
		end;
		local match_len = span[0][2] - span[0][1];
		local repl_len = math.min(repl_r.n, match_len);
		for i1 = 0, repl_len - 1 do
			str[span[0][1] + i1] = repl_r[i1 + 1];
		end;
		local i1 = span[0][1] + repl_len;
		i0 = span[0][2];
		if match_len > repl_r.n then
			for i2 = 1, match_len - repl_r.n do
				table.remove(str, i1);
				incr -= 1;
				i0 -= 1;
			end;
		elseif repl_r.n > match_len then
			for i2 = 1, repl_r.n - match_len do
				table.insert(str, i1 + i2 - 1, repl_r[repl_len + i2]);
				incr += 1;
				i0 += 1;
			end;
		end;
		if match_len <= 0 then
			i0 += 1;
		end;
		count += 1;
		if n < count + 1 then
			break;
		end;
	end;
	return from_str_arr(str), count;
end);

re_m.split = check_re('RegEx', 'split', function(self, str, n)
	if tonumber(n) then
		n = tonumber(n);
		if n <= -1 or n ~= n then
			n = math.huge;
		end;
	elseif n ~= nil then
		error(string.format("invalid argument #3 to 'split' (number expected, got %s)", typeof(n)), 3);
	else
		n = math.huge;
	end;
	str = to_str_arr(str);
	local i, count = 1, 0;
	local ret = { };
	local prev_empty = 0;
	while i <= str.n + 1 do
		count += 1;
		local span = n >= count and re_rawfind(self.token, str, i, self.flags, self.verb_flags, false);
		if not span then
			break;
		end;
		table.insert(ret, utf8_sub(str.s, i - prev_empty, span[0][1]));
		prev_empty = span[0][1] >= span[0][2] and 1 or 0;
		i = span[0][2] + prev_empty;
	end;
	table.insert(ret, string.sub(str.s, utf8.offset(str.s, i - prev_empty)));
	return ret;
end);
