--[[

Este módulo tem como objetivo fornecer acesso a funções básicas de sequência (string).

A maioria das funções fornecidas aqui podem ser invocadas com parâmetros nomeados,
parâmetros sem nome ou uma mistura. Se parâmetros nomeados forem usados, o Mediawiki irá
remover automaticamente qualquer espaço em branco à esquerda ou à direita do parâmetro.
Dependendo do uso pretendido, pode ser vantajoso preservar ou
remover esses espaços em branco.

Opções globais
    ignore_errors: Se definido como 'true' ou 1, qualquer condição de erro resultará em
        uma sequência (string) vazia sendo retornada em vez de uma mensagem de erro.

    error_category: Se ocorrer um erro, especifica o nome de uma categoria a ser
        incluída com a mensagem de erro. A categoria padrão é
        [Categoria:!Erros reportados pelo Módulo String].

    no_category: Se definido como 'true' ou 1, nenhuma categoria será adicionada se um erro 
        for gerado.

Os testes unitários para este módulo estão disponíveis em Módulo:String/Testes/tests.
]]

local str = {}

--[[
len

Esta função retorna o comprimento da sequência (string) de destino.

Uso:
{{#invoke:String/Testes|len|string_de_destino|}}
ou
{{#invoke:String/Testes|len|s=string_de_destino}}

Parâmetros
    s: A sequência (string) cujo comprimento será relatado

Se invocada usando parâmetros nomeados, o Mediawiki removerá automaticamente qualquer espaço em branco inicial ou 
final da sequência (string) de destino.
]]
function str.len( frame )
	local new_args = str._getParameters( frame.args, {'s'} )
	local s = new_args['s'] or ''
	return mw.ustring.len( s )
end

--[[
sub

Esta função retorna uma subsequência (substring) da sequência (string) de destino em índices especificados.

Usao:
{{#invoke:String/Testes|sub|string_de_destino|índice_inicial|índice_final}}
ou
{{#invoke:String/Testes|sub|s=string_de_destino|i=índice_inicial|j=índice_final}}

Parâmetros
    s: A sequência (string) para retornar um subconjunto de
    i: O primeiro índice da subsequência (substring) a ser retornada, o padrão é 1.
    j: O último índice da sequência (string) a ser retornada, o padrão é o último caractere.

O primeiro caractere da sequência (string) recebe um índice de 1. Se i ou j 
for um valor negativo, ele será interpretado da mesma forma que selecionar um caractere 
contando a partir do final da sequência (string). Portanto, um valor -1 é o mesmo que 
selecionar o último caractere da sequência (string).

Se os índices solicitados estiverem fora do intervalo para a sequência especificada, um erro será 
relatado.
]]
function str.sub( frame )
	local new_args = str._getParameters( frame.args, { 's', 'i', 'j' } )
	local s = new_args['s'] or ''
	local i = tonumber( new_args['i'] ) or 1
	local j = tonumber( new_args['j'] ) or -1

	local len = mw.ustring.len( s )

	-- Converte negativos para verificação de intervalo
	if i < 0 then
		i = len + i + 1
	end
	if j < 0 then
		j = len + j + 1
	end

	if i > len or j > len or i < 1 or j < 1 then
		return str._error( 'Índice de subconjunto de string fora do intervalo' )
	end
	if j < i then
		return str._error( 'Índices de subconjunto de string fora de ordem' )
	end

	return mw.ustring.sub( s, i, j )
end

--[[
Esta função implementa esses recursos de {{Str sub old/Testes}} e é mantida 
para manter essas predefinições mais antigas.
]]
function str.sublength( frame )
	local i = tonumber( frame.args.i ) or 0
	local len = tonumber( frame.args.len )
	return mw.ustring.sub( frame.args.s, i + 1, len and ( i + len ) )
end

--[[
_match

Esta função retorna uma substring da sequência (string) de origem que corresponde a um
padrão especificado. Ela é exportada para uso em outros módulos

Usao:
strmatch = require("Módulo:String/Testes")._match
sresult = strmatch( s, pattern, start, match, plain, nomatch )

Parâmetros
    s: A sequência (string) a ser pesquisada
    pattern: O padrão ou sequência (string) para encontrar dentro da sequência (string)
    start: O índice na sequência (string) de origem para iniciar a pesquisa. O primeiro
        caractere da sequência (string) tem índice 1. O padrão é 1.
    match: Em alguns casos pode ser possível fazer múltiplas correspondências em uma única 
        sequência (string). Isto especifica qual correspondência retornar, onde a primeira correspondência é
        match= 1.  Se um número negativo for especificado, uma correspondência será retornada 
        contando a partir da última correspondência. Portanto, match = -1 é o mesmo que solicitar 
        a última correspondência. O padrão é 1.
    plain: Um sinalizador indicando que o padrão deve ser entendido como 
        texto simples. O padrão é false.
    nomatch: Se nenhuma correspondência for encontrada, gera o valor de "nomatch" em vez de um erro.

Para obter informações sobre a construção de padrões Lua, uma forma de [expressão regular], ver:

* http://www.lua.org/manual/5.1/manual.html#5.4.1
* http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns
* http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns

]]
-- Esta sub-rotina é exportada para uso em outros módulos
function str._match( s, pattern, start, match_index, plain_flag, nomatch )
	if s == '' then
		return str._error( 'A string de destino está vazia' )
	end
	if pattern == '' then
		return str._error( 'A sequência de padrões está vazia' )
	end
	start = tonumber(start) or 1
	if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then
		return str._error( 'O início solicitado está fora do intervalo' )
	end
	if match_index == 0 then
		return str._error( 'O índice de correspondência está fora do intervalo' )
	end
	if plain_flag then
		pattern = str._escapePattern( pattern )
	end

	local result
	if match_index == 1 then
		-- Encontrar a primeira correspondência é um caso simplese
		result = mw.ustring.match( s, pattern, start )
	else
		if start > 1 then
			s = mw.ustring.sub( s, start )
		end

		local iterator = mw.ustring.gmatch(s, pattern)
		if match_index > 0 then
			-- Pesquisa direta
			for w in iterator do
				match_index = match_index - 1
				if match_index == 0 then
					result = w
					break
				end
			end
		else
			-- Pesquisa reversa
			local result_table = {}
			local count = 1
			for w in iterator do
				result_table[count] = w
				count = count + 1
			end

			result = result_table[ count + match_index ]
		end
	end

	if result == nil then
		if nomatch == nil then
			return str._error( 'Correspondência não encontrada' )
		else
			return nomatch
		end
	else
		return result
	end
end

--[[
match

Esta função retorna uma subsequência (substring) a partir da sequência (string) de origem que corresponde a um
padrão especificado.

Uso:
{{#invoke:String/Testes|match|string_de_origem|string_de_padrão|índice_inicial|número_de_correspondência|sinalizador_de_plain|saída_de_nomatch}}
ou
{{#invoke:String/Testes|match|s=string_de_origem|pattern=string_de_padrão|start=índice_inicial
    |match=número_de_correspondência|plain=sinalizador_de_plain|nomatch=saída_de_nomatch}}

Parâmetros
    s: A sequência (string) para pesquisar
    pattern: O padrão ou sequência (string) para encontrar dentro da sequência (string)
    start: O índice na sequência (string) de origem para iniciar a pesquisa. O primeiro
        caractere da sequência (string) tem índice 1. O padrão é 1.
    match: Em alguns casos pode ser possível fazer múltiplas correspondências em uma única 
        sequência (string). Isto especifica qual correspondência retornar, onde a primeira correspondência é
        match= 1.  Se um número negativo for especificado, uma correspondência será retornada 
        contando a partir da última correspondência. Portanto, match = -1 é o mesmo que solicitar 
        a última correspondência. O padrão é 1.
    plain: Um sinalizador indicando que o padrão deve ser entendido como 
        texto simples. O padrão é false.
    nomatch: Se nenhuma correspondência for encontrada, gera o valor de "nomatch" em vez de um erro.

Se invocado usando parâmetros nomeados, o Mediawiki removerá automaticamente qualquer espaço em branco inicial ou 
final da sequência (string) de destino.  Em algumas circunstâncias isto é desejável, em 
outros casos, pode-se querer preservar o espaço em branco.

Se o número_de_correspondência ou o índice_inicial estiverem fora do intervalo da sequência (string) que está sendo consultada, então
esta função gera um erro. Um erro também será gerado se nenhuma correspondência for encontrada.
Se adicionarmos o parâmetro ignore_errors=true, então o erro será suprimido e
uma sequência (string) vazia será retornada em qualquer falha.

Para obter informações sobre a construção de padrões Lua, uma forma de [expressão regular], ver:

* http://www.lua.org/manual/5.1/manual.html#5.4.1
* http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns
* http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns

]]
-- Este é o ponto de entrada para #invoke:String/Testes|match
function str.match( frame )
	local new_args = str._getParameters( frame.args, {'s', 'pattern', 'start', 'match', 'plain', 'nomatch'} )
	local s = new_args['s'] or ''
	local start = tonumber( new_args['start'] ) or 1
	local plain_flag = str._getBoolean( new_args['plain'] or false )
	local pattern = new_args['pattern'] or ''
	local match_index = math.floor( tonumber(new_args['match']) or 1 )
	local nomatch = new_args['nomatch']

	return str._match( s, pattern, start, match_index, plain_flag, nomatch )
end

--[[
pos

Esta função retorna um único caractere da sequência (string) de destino na posição pos.

Uso:
{{#invoke:String/Testes|pos|string_de_destino|valor_do_índice}}
ou
{{#invoke:String/Testes|pos|target=string_de_destino|pos=valor_do_índice}}

Parâmetros
    target: A sequência (string) para pesquisar
    pos: O índice do caractere para retornar

Se invocada usando parâmetros nomeados, o Mediawiki removerá automaticamente qualquer espaço em branco inicial ou 
final da sequência (string) de destino. Em algumas circunstâncias isto é desejável, em 
outros casos pode-se querer preservar o espaço em branco.

O primeiro caractere tem um valor de índice de 1.

Se alguém solicitar um valor negativo, esta função selecionará um caractere contando regressivamente 
a partir do final da sequência (string). Em outras palavras, pos = -1 é o mesmo que pedir o último caractere.

Um valor solicitado igual a zero ou um valor maior que o comprimento da sequência (string) retorna um erro.
]]
function str.pos( frame )
	local new_args = str._getParameters( frame.args, {'target', 'pos'} )
	local target_str = new_args['target'] or ''
	local pos = tonumber( new_args['pos'] ) or 0

	if pos == 0 or math.abs(pos) > mw.ustring.len( target_str ) then
		return str._error( 'Índice de string fora do intervalo' )
	end

	return mw.ustring.sub( target_str, pos, pos )
end

--[[
str_find

Esta função duplica o comportamento de {{Str find/Testes}}, incluindo todas as suas peculiaridades.
Isto é fornecido para suportar predefinições existentes, mas não é recomendado para
novos códigos e predefinições. Recomenda-se que o novo código use a função "find".

Retorna o primeiro índice em "source" que corresponde a "target". A indexação é baseada em 1,
e a função retorna -1 se a sequência (string) "target" não estiver presente em "source".

Nota importante: Se a sequência (string) "target" estiver vazia/ausente, esta função retorna um
valor de "1", que geralmente é um comportamento inesperado e deve ser levado em consideração
separadamente.
]]
function str.str_find( frame )
	local new_args = str._getParameters( frame.args, {'source', 'target'} )
	local source_str = new_args['source'] or ''
	local target_str = new_args['target'] or ''

	if target_str == '' then
		return 1
	end

	local start = mw.ustring.find( source_str, target_str, 1, true )
	if start == nil then
		start = -1
	end

	return start
end

--[[
find

Esta função permite pesquisar uma sequência (string) ou padrão de destino dentro de outra 
sequência (string).

Uso:
{{#invoke:String/Testes|find|string_de_origem|string_de_destino|índice_inicial|sinalizador_deplain}}
ou
{{#invoke:String/Testes|find|source=string_de_origem|target=string_de_destino|start=índice_inicial|plain=sinalizador_de_plain}}

Parâmetros
    source: A sequência (string) para pesquisar
    target: A sequência (string) ou padrão a ser encontrado na fonte
    start: O índice dentro da sequência (string) de origem para iniciar a pesquisa, o padrão é 1
    plain: Sinalizador booleano indicando que o alvo deve ser entendido como texto 
        simples e não como uma expressão regular estilo Lua, o padrão é true

Se invocada usando parâmetros nomeados, o Mediawiki removerá automaticamente qualquer espaço em branco inicial ou 
final da sequência (string) de destino. Em algumas circunstâncias isto é desejável, em 
outros casos pode-se querer preservar o espaço em branco.

Esta função retorna o primeiro índice >= "start" onde "target" pode ser encontrado
dentro de "source". Os índices são baseados em 1. Se "target" não for encontrado, então esta 
função retorna 0. Se "source" ou "target" estiverem ausentes/vazios, esta 
função também retornará 0.

Esta função deve ser segura para sequências (strings) UTF-8.
]]
function str.find( frame )
	local new_args = str._getParameters( frame.args, {'source', 'target', 'start', 'plain' } )
	local source_str = new_args['source'] or ''
	local pattern = new_args['target'] or ''
	local start_pos = tonumber(new_args['start']) or 1
	local plain = new_args['plain'] or true

	if source_str == '' or pattern == '' then
		return 0
	end

	plain = str._getBoolean( plain )

	local start = mw.ustring.find( source_str, pattern, start_pos, plain )
	if start == nil then
		start = 0
	end

	return start
end

--[[
replace

Esta função permite substituir uma sequência (string) ou padrão de destino dentro de outra 
sequência (string).

Uso:
{{#invoke:String/Testes|replace|string_de_origem|string_de_padrão|string_de_substituição|contagem_de_substituições|sinalizador_de_plain}}
ou
{{#invoke:String/Testes|replace|source=string_de_origem|pattern=string_de_padrão|replace=string_de_substituição|
   count=contagem_de_substituições|plain=string_de_substituição}}

Parâmetros
    source: A sequência (string) para pesquisar
    pattern: A sequência (string) ou padrão a ser encontrado na fonte
    replace: O texto de substituição
    count: O número de ocorrências a serem substituídas, o padrão é todas.(all).
    plain: Sinalizador booleano indicando que o padrão deve ser entendido como texto 
        simples e não como uma expressão regular no estilo Lua, o padrão é verdadeiro (true)
]]
function str.replace( frame )
	local new_args = str._getParameters( frame.args, {'source', 'pattern', 'replace', 'count', 'plain' } )
	local source_str = new_args['source'] or ''
	local pattern = new_args['pattern'] or ''
	local replace = new_args['replace'] or ''
	local count = tonumber( new_args['count'] )
	local plain = new_args['plain'] or true

	if source_str == '' or pattern == '' then
		return source_str
	end
	plain = str._getBoolean( plain )

	if plain then
		pattern = str._escapePattern( pattern )
		replace = mw.ustring.gsub( replace, "%%", "%%%%" ) -- Só precisa escapar das sequências de substituição.
	end

	local result

	if count ~= nil then
		result = mw.ustring.gsub( source_str, pattern, replace, count )
	else
		result = mw.ustring.gsub( source_str, pattern, replace )
	end

	return result
end

--[[
    função simples para canalizar string.rep para predefinições.
]]
function str.rep( frame )
	local repetitions = tonumber( frame.args[2] )
	if not repetitions then
		return str._error( 'a função rep espera um número como segundo parâmetro, recebido "' .. ( frame.args[2] or '' ) .. '"' )
	end
	return string.rep( frame.args[1] or '', repetitions )
end

--[[
escapePattern

Esta função escapa caracteres especiais a partir de um padrão de sequência (string) Lua. Veja [1]
para obter detalhes sobre como os padrões funcionam.

[1] https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns

Uso
{{#invoke:String/Testes|escapePattern|string_de_padrão}}

Parâmetros
    pattern_string: A sequência (string) de padrão para escapar.
]]
function str.escapePattern( frame )
	local pattern_str = frame.args[1]
	if not pattern_str then
		return str._error( 'Nenhuma string de padrão especificada' )
	end
	local result = str._escapePattern( pattern_str )
	return result
end

--[[
count
Esta função conta o número de ocorrências de uma sequência (string) em outra.
]]
function str.count(frame)
	local args = str._getParameters(frame.args, {'source', 'pattern', 'plain'})
	local source = args.source or ''
	local pattern = args.pattern or ''
	local plain = str._getBoolean(args.plain or true)
	if plain then
		pattern = str._escapePattern(pattern)
	end
	local _, count = mw.ustring.gsub(source, pattern, '')
	return count
end

--[[
endswith
Esta função determina se uma sequência (string) termina com outra sequência (string).
]]
function str.endswith(frame)
	local args = str._getParameters(frame.args, {'source', 'pattern'})
	local source = args.source or ''
	local pattern = args.pattern or ''
	if pattern == '' then
		-- Todas as sequências (strings) terminam com uma sequência (string) vazia.
		return "yes"
	end
	if mw.ustring.sub(source, -mw.ustring.len(pattern), -1) == pattern then
		return "yes"
	else
		return ""
	end
end

--[[
join

Une todos os argumentos não vazios; o primeiro argumento é o separador.
Uso:
{{#invoke:String/Testes|join|sep|um|dois|três}}
]]
function str.join(frame)
	local args = {}
	local sep
	for _, v in ipairs( frame.args ) do
		if sep then
			if v ~= '' then
				table.insert(args, v)
			end
		else
			sep = v
		end
	end
	return table.concat( args, sep or '' )
end

--[[
Função auxiliar que preenche a lista de argumentos, visto que o usuário pode precisar usar uma combinação de
parâmetros nomeados e sem nome. Isto é relevante porque os parâmetros nomeados não são
idênticos aos parâmetros sem nome devido ao corte de sequências (strings) e ao lidar com sequências (strings)
às vezes queremos preservar ou remover esse espaço em branco, dependendo do aplicativo.
]]
function str._getParameters( frame_args, arg_list )
	local new_args = {}
	local index = 1
	local value

	for _, arg in ipairs( arg_list ) do
		value = frame_args[arg]
		if value == nil then
			value = frame_args[index]
			index = index + 1
		end
		new_args[arg] = value
	end

	return new_args
end

--[[
Função auxiliar para lidar com mensagens de erro.
]]
function str._error( error_str )
	local frame = mw.getCurrentFrame()
	local error_category = frame.args.error_category or '!Erros reportados pelo Módulo String'
	local ignore_errors = frame.args.ignore_errors or false
	local no_category = frame.args.no_category or false

	if str._getBoolean(ignore_errors) then
		return ''
	end

	local error_str = '<strong class="error">Erro no módulo String: ' .. error_str .. '</strong>'
	if error_category ~= '' and not str._getBoolean( no_category ) then
		error_str = '[[Categoria:' .. error_category .. ']]' .. error_str
	end

	return error_str
end

--[[
Função auxiliar para interpretar sequências (strings) booleanas
]]
function str._getBoolean( boolean_str )
	local boolean_value

	if type( boolean_str ) == 'string' then
		boolean_str = boolean_str:lower()
		if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0'
				or boolean_str == '' then
			boolean_value = false
		else
			boolean_value = true
		end
	elseif type( boolean_str ) == 'boolean' then
		boolean_value = boolean_str
	else
		error( 'Nenhum valor booleano encontrado' )
	end
	return boolean_value
end

--[[
Função auxiliar que escapa todos os caracteres de padrão para que eles sejam tratados
como texto simples.
]]
function str._escapePattern( pattern_str )
	return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" )
end

return str