local utf8 = {}
function utf8.next_raw(s, i)
	if not i then
		if #s == 0 then
			return nil
		end
		return 1, true
	end
	if i > #s then
		return
	end
	local c = s:byte(i)
	if 0 <= c and c <= 127 then
		i = i + 1
	elseif 194 <= c and c <= 223 then
		i = i + 2
	elseif 224 <= c and c <= 239 then
		i = i + 3
	elseif 240 <= c and c <= 244 then
		i = i + 4
	else
		return i + 1, false
	end
	if i > #s then
		return
	end
	return i, true
end
utf8.next = utf8.next_raw
function utf8.byte_indices(s, previ)
	return utf8.next, s, previ
end
function utf8.len(s)
	assert(s, "bad argument #1 to 'len' (string expected, got nil)")
	local len = 0
	for _ in utf8.byte_indices(s) do
		len = len + 1
	end
	return len
end
function utf8.byte_index(s, target_ci)
	if target_ci < 1 then
		return
	end
	local ci = 0
	for i in utf8.byte_indices(s) do
		ci = ci + 1
		if ci == target_ci then
			return i
		end
	end
	assert(target_ci > ci, "invalid index")
end
function utf8.char_index(s, target_i)
	if target_i < 1 or target_i > #s then
		return
	end
	local ci = 0
	for i in utf8.byte_indices(s) do
		ci = ci + 1
		if i == target_i then
			return ci
		end
	end
	error("invalid index")
end
function utf8.prev(s, nexti)
	nexti = nexti or #s + 1
	if nexti <= 1 or nexti > #s + 1 then
		return
	end
	local lasti, lastvalid = utf8.next(s)
	for i, valid in utf8.byte_indices(s) do
		if i == nexti then
			return lasti, lastvalid
		end
		lasti, lastvalid = i, valid
	end
	if nexti == #s + 1 then
		return lasti, lastvalid
	end
	error("invalid index")
end
function utf8.byte_indices_reverse(s, nexti)
	if #s < 200 then
		return utf8.prev, s, nexti
	else
		local t = {}
		for i in utf8.byte_indices(s) do
			if nexti and nexti <= i then
				break
			end
			table.insert(t, i)
		end
		local i = #t + 1
		return function()
			i = i - 1
			return t[i]
		end
	end
end
function utf8.sub(s, start_ci, end_ci)
	assert(1 <= start_ci)
	assert(not end_ci or 0 <= end_ci)
	local ci = 0
	local start_i, end_i
	for i in utf8.byte_indices(s) do
		ci = ci + 1
		if ci == start_ci then
			start_i = i
		end
		if ci == end_ci then
			end_i = i
		end
	end
	if not start_i then
		assert(start_ci > ci, "invalid index")
		return ""
	end
	if end_ci and not end_i then
		if end_ci < start_ci then
			return ""
		end
		assert(end_ci > ci, "invalid index")
	end
	return s:sub(start_i, end_i and end_i - 1)
end
function utf8.contains(s, i, sub)
	if i < 1 or i > #s then
		return nil
	end
	for si = 1, #sub do
		if s:byte(i + si - 1) ~= sub:byte(si) then
			return false
		end
	end
	return true
end
function utf8.count(s, sub)
	assert(0 < #sub)
	local count = 0
	local i = 1
	while i do
		if utf8.contains(s, i, sub) then
			count = count + 1
			i = i + #sub
			if i > #s then
				break
			end
		else
			i = utf8.next(s, i)
		end
	end
	return count
end
function utf8.isvalid(s, i)
	local c = s:byte(i)
	if not c then
		return false
	elseif 0 <= c and c <= 127 then
		return true
	elseif 194 <= c and c <= 223 then
		local c2 = s:byte(i + 1)
		return c2 and 128 <= c2 and c2 <= 191
	elseif 224 <= c and c <= 239 then
		local c2 = s:byte(i + 1)
		local c3 = s:byte(i + 2)
		if c == 224 then
			return c2 and c3 and 160 <= c2 and c2 <= 191 and 128 <= c3 and c3 <= 191
		elseif 225 <= c and c <= 236 then
			return c2 and c3 and 128 <= c2 and c2 <= 191 and 128 <= c3 and c3 <= 191
		elseif c == 237 then
			return c2 and c3 and 128 <= c2 and c2 <= 159 and 128 <= c3 and c3 <= 191
		elseif 238 <= c and c <= 239 then
			if c == 239 and c2 == 191 and (c3 == 190 or c3 == 191) then
				return false
			end
			return c2 and c3 and 128 <= c2 and c2 <= 191 and 128 <= c3 and c3 <= 191
		end
	elseif 240 <= c and c <= 244 then
		local c2 = s:byte(i + 1)
		local c3 = s:byte(i + 2)
		local c4 = s:byte(i + 3)
		if c == 240 then
			return c2 and c3 and c4 and 144 <= c2 and c2 <= 191 and 128 <= c3 and c3 <= 191 and 128 <= c4 and c4 <= 191
		elseif 241 <= c and c <= 243 then
			return c2 and c3 and c4 and 128 <= c2 and c2 <= 191 and 128 <= c3 and c3 <= 191 and 128 <= c4 and c4 <= 191
		elseif c == 244 then
			return c2 and c3 and c4 and 128 <= c2 and c2 <= 143 and 128 <= c3 and c3 <= 191 and 128 <= c4 and c4 <= 191
		end
	end
	return false
end
function utf8.next_valid(s, i)
	local valid
	i, valid = utf8.next_raw(s, i)
	while i and (not valid or not utf8.isvalid(s, i)) do
		i, valid = utf8.next(s, i)
	end
	return i
end
function utf8.valid_byte_indices(s)
	return utf8.next_valid, s
end
function utf8.validate(s)
	for i, valid in utf8.byte_indices(s) do
		if not valid or not utf8.isvalid(s, i) then
			error(string.format("invalid utf8 char at #%d", i))
		end
	end
end
local table_lookup = function(s, i, j, t)
	return t[s:sub(i, j)]
end
function utf8.replace(s, f, ...)
	if type(f) == "table" then
		return utf8.replace(s, table_lookup, f)
	end
	if s == "" then
		return s
	end
	local t = {}
	local lasti = 1
	for i in utf8.byte_indices(s) do
		local nexti = utf8.next(s, i) or #s + 1
		local repl = f(s, i, nexti - 1, ...)
		if repl then
			table.insert(t, s:sub(lasti, i - 1))
			table.insert(t, repl)
			lasti = nexti
		end
	end
	table.insert(t, s:sub(lasti))
	return table.concat(t)
end
local replace_invalid = function(s, i, j, repl_char)
	if not utf8.isvalid(s, i) then
		return repl_char
	end
end
function utf8.sanitize(s, repl_char)
	repl_char = repl_char or "�"
	return utf8.replace(s, replace_invalid, repl_char)
end
return utf8
