Módulo:Categorizar

local m = {}

-- remove "Categoria:" do inicio se tiver
local rmcat = function(cat)
  if not cat then return end
  local a, b = string.find(cat, '[Cc]ategoria:')
  if a == 1 then
    return string.sub(cat, b + 1)
  end
  return cat
end

-- verifica se a categoria existe
local existe = function(cat)
  if not cat then return end
  local page = mw.title.new(cat, 14)
  if page.exists then
    return '[[Categoria:' .. cat .. ']]'
  end
end

-- verifica se é um ano válido e converte a.C. para número negativo
local num_ano = function(ano)
  local a, b = string.find(ano, '^%-?%d+')
  -- retorna nil se ano não for válido
  if a == nil then return end
  local ac = string.find(string.sub(ano, b + 1), '^ ?a%.?[Cc]%.?$')
  ano = tonumber(string.sub(ano, a, b))
  if ac and ano > 0 then
    ano = -ano
  end
  return ano
end

-- transforma um número em número romano
local romano = function(num)
  if num > 3999 or num < 1 then return end
  local nr = {[1]='I', [5]='V', [10]='X', [50]='L', [100]='C', [500]='D', [1000]='M'}
  local resp = {}
  while num > 0 do
    local base = math.pow(10, math.floor(math.log10(num)))
    local mult = math.floor(num / base)
    if mult == 9 then
      table.insert(resp, nr[base] .. nr[base * 10])
      num = num - 9 * base
    elseif mult > 5 then
      table.insert(resp, nr[base * 5])
      num = num - 5 * base
    elseif mult == 4 then
      table.insert(resp, nr[base] .. nr[base * 5])
      num = num - 4 * base
    else
      table.insert(resp, nr[base])
      num = num - base
    end
  end
  return table.concat(resp)
end

-- verifica se o ano é válido e padroniza a indicação a.C.
local valida_ano = function(ano)
  if string.find(ano, '^%d+$') then
    return ano
  elseif string.find(ano, '^%-%d+$') then
    return math.abs(ano) .. ' a.C.'
  end
  local ano, ac = string.match(ano, '^(%d+) ?(a%.?[Cc]%.?)$')
  if ano then
    return ano .. ' a.C.'
  end
end

-- retorna a categoria da década
m.ano_decada = function(cat, ano)
  ano = num_ano(ano)
  if ano == nil then return end
  local decada = math.floor(ano / 10) * 10
  if ano < 0 then
    decada = decada + 10 .. ' a.C.'
  end
  if string.find(cat, ' de$') then
    return string.sub(cat, 1, -3) .. 'da década de ' .. decada
  elseif string.find(cat, ' em$') then
    return string.sub(cat, 1, -3) .. 'na década de ' .. decada
  else
    return cat .. ' década de ' .. decada
  end
end

-- retorna a categoria do século
m.ano_seculo = function(cat, ano)
  ano = num_ano(ano)
  if ano == nil then return end
  
  -- ano fora dos limites de tempo
  if ano > 3000 or ano < -3000 then
    if string.find(cat, ' em$') then
      cat = string.sub(cat, 1, -3)
    end
    return cat .. (ano > 0 and 'após o século XXX' or 'antes do século XXX a.C.')
  end

  local num = (math.floor(math.abs(ano) / 100) + 1)
  local seculo = romano(num)
  if ano < 0 then
    seculo = seculo .. ' a.C.'
  end
  if string.find(cat, ' de$') then
    return string.sub(cat, 1, -3) .. 'do século ' .. seculo
  elseif string.find(cat, ' em$') then
    return string.sub(cat, 1, -3) .. 'no século ' .. seculo
  else
    return cat .. ' século ' .. seculo
  end
end

------ função principal ------
m.categorizar = function(args)
  local prefixo = args['prefixo']
  local sufixo = args['sufixo'] or ''
  local ano = args['ano']
  local cat_erro = args['erro'] and rmcat(args['erro'])
  local cats = {}

  -- tratamento de erros
  cat_erro = cat_erro and '[[Categoria:' .. cat_erro .. ']]'
    or '[[Categoria:!Páginas com erro nos parâmetros de categorização]]'
  if ano then
    local valido = valida_ano(ano)
    if valido then
      ano = valido
    else
      return cat_erro
    end
  end

  -- verifica parâmetros de ano mínimo e ano máximo
  for k, v in pairs(args) do
    local antes = string.match(k, '^antes de (%-?%d+ ?a?%.?[Cc]?%.?)$')
    local apos = string.match(k, '^após (%-?%d+ ?a?%.?[Cc]?%.?)$')
    if (antes or apos) and ano then
      local limite = num_ano(antes or apos)
      local num = num_ano(ano)
      if not limite or not num then break end
      if antes and num < limite or apos and num > limite then
        return '[[Categoria:' .. v .. ']]'
      end
    end
  end

  -- primeira passagem pelas opções de categorias
  for _, cat in ipairs(args) do
    cat = rmcat(cat)
    if prefixo then
      cat = rmcat(prefixo) .. ' ' .. cat
    end
    if (cat == '' and sufixo == '') or string.find(cat, '[{}%[%]]') then
      return cat_erro
    elseif ano then
      table.insert(cats, cat)
    else
      if sufixo ~= '' then
        cat = cat .. ' ' .. sufixo
      end
      local encontrada = existe(cat)
      if encontrada then
        return encontrada
      end
    end
  end

  -- categorias com ano, década e século
  if ano then
  	
    -- para poder usar somente ano e sufixo
    if not cats[1] and sufixo ~= '' then
      cats[1] = ''
    end

    -- ano
    for _, cat in ipairs(cats) do
      cat = cat .. ' ' .. ano
      if sufixo ~= '' then
        cat = cat .. ' ' .. sufixo
      end
      local encontrada = existe(cat)
      if encontrada then
        return encontrada
      end
    end

    -- década do ano
    for _, cat in ipairs(cats) do
      cat = m.ano_decada(cat, ano)
      if sufixo ~= '' then
        cat = cat .. ' ' .. sufixo
      end
      local encontrada = existe(cat)
      if encontrada then
        return encontrada
      end
    end

    -- século do ano
    for _, cat in ipairs(cats) do
      cat = m.ano_seculo(cat, ano)
      if sufixo ~= '' then
        cat = cat .. ' ' .. sufixo
      end
      local encontrada = existe(cat)
      if encontrada then
        return encontrada
      end
    end
  end

  -- nenhuma categoria encontrada, usa o parâmetro 'nenhuma' se estiver preenchido
  if args['nenhuma'] then
    cat = rmcat(args['nenhuma'])
    -- não testa se a categoria existe desta vez
    return '[[Categoria:' .. cat .. ']]'
  end
end

-- funções para serem usadas com #invoke

-- função principal para ser usada na {{categorizar}}
m.principal = function(frame)
  local args = next(frame.args) ~= nil and frame.args or frame:getParent().args or {}
  return m.categorizar(args)
end

-- como a m.predef, só que retorna o link [[:Categoria:...]]
m.link = function(frame)
  local cat = m.categorizar(frame.args)
  if cat then
    cat = string.gsub(cat, '^%[%[C', '[[:C')
    return cat
  end
end

return m