local math_mod = require( "Módulo:Math" )

local p = {}

local current_page = mw.title.getCurrentTitle()local page_name = mw.uri.encode( current_page.prefixedText, 'WIKI' );
local coord_link = '//tools.wmflabs.org/geohack/geohack.php?pagename=' .. page_name .. '&params='
--Carregando a lista em /Au/Aux/A
local gdata
local success, resultado = pcall (mw.loadData, "Módulo:Bandeira/Dados" )
if success then
    gdata = resultado
else
    -- Banco de dados minimo em caso de bug em Módulo:Língua/Dados
    gdata={}
    gdata.data={};
    gdata.data[142]={qid="Q45", label="Portugal", genre="ms"}
end

local i18n = {
    N = 'N',
    Nlong = 'norte',
    W = 'O',
    Wlong = 'oeste',
    E = 'L',
    Elong = 'leste',
    S = 'S',
    Slong = 'sul',
    degrees = '° ',
    minutes = '′ ',
    seconds = '″ ',
    geohackurl = 'http://tools.wmflabs.org/geohack/geohack.php?language=pt',
    tooltip = 'Mapas, vistas aéreas, etc.',
    errorcat = '!Páginas com as etiquetas de coordenadas malformadas',
    sameaswikidata = '!Páginas com coordenadas similares no Wikidata',
    notaswikidata = '!Páginas com coordenadas diferentes no Wikidata',
    nowikidata = '!Artigos com coordenadas por transcrever a Wikidata',
    throughwikidata = '!Artigos com coordenadas no Wikidata',
    invalidFormat = 'formato inválido',                        -- 'invalid coordinate format',
    invalidNSEW = 'orientação inválida, deve ser "N", "S", "E" or "W"',       -- 'invalid direction should be "N", "S", "E" or "O"',
    invalidNS = 'orientação de latitude inválida, deve ser "N" ou "S"',       -- 'could not find latitude direction (should be N or S)',
    invalidEW = 'orientação de longitude inválida, deve ser "E" ou "W"',   -- 'could not find longitude direction (should be W or E) ',
    noCardinalDirection = 'orientação cardinal não informada',            -- 'no cardinal direction found in coordinates',
    invalidDirection = 'direção inválida',                      -- 'invalid direction',
    latitude90 = 'latitude > 90',
    longitude360 = 'longitude > 360',
    minSec60 = 'minutos ou segundos > 60',
    negativeCoode = 'em formato dms os graus devem ser positivos',         -- 'dms coordinates should be positive',
    dmIntergers = 'graus e minutos devem ser números inteiros',        -- 'degrees and minutes should be integers',
    tooManyParam = 'muitos parâmetros para latitude ou longitude',     -- 'too many parameters for coordinates',
    coordMissing = 'latitude ou longitude ausente',                -- 'latitude or longitude missing',
    invalidGlobe = 'globo inválido : ',                        -- 'invalid globe:',
}
local coordParse = {
    NORTH = 'N',
    NORTE = 'N',
    EAST = 'E',
    LESTE = 'E',
    L = 'E',
    WEST = 'W',
    W = 'W',
    O = 'W',
    OESTE = 'W',
    SOUTH = 'S',
    SUL = 'S',
}

--Ajuda:Function_genre (gênero)
local genre = {
    ms =    {le="a ",  du="do ",    de="de ",  ao="ao ",  em="em "},
    msa =   {le="a ",   du="da ",  de="de",   ao="à ", em="em "},
    msi =   {le="",     du="do ",    de="de ",  ao="à ",   em="à "},
    msia =  {le="",     du="do ",     de="de ",   ao="à ",   em="à "},
    msiae = {le="",     du="do ",     de="de ",   ao="à ",   em="em "},
    fs =    {le="a ",  du="da ", de="de ",  ao="à ",em="em "},
    fsa =   {le="a ",   du="da ",  de="de ",   ao="à ", em="em "},
    fsi =   {le="",     du="de ",    de="de ",  ao="à ",   em="à "},
    fsia =  {le="",     du="da ",     de="de ",   ao="à ",   em="à "},
    mp =    {le="os ", du="dos ",   de="dos ", ao="aos ", en="em "},
    fp =    {le="as ", du="das ",   de="das ", ao="as ", em="em "}
}

local globedata =     {
    --[[ notes:
        radius in kilometers (especially imprecise for non spheric bodies)
        defaultdisplay is currently disabled, activate it ?
    ]]--
    ariel = {radius = 580, defaultdisplay = 'dec east' },
    callisto =  {radius = 2410, defaultdisplay = 'dec west' },
    ceres =  {radius = 470, defaultdisplay = 'dec east' },
    charon =  {radius = 1214, defaultdisplay = 'dec east' },
    deimos =  {radius = 7, defaultdisplay = 'dec west' },
    dione =  {radius = 560, defaultdisplay = 'dec west' },
    enceladus =  {radius = 255, defaultdisplay = 'dec west' },
    ganymede =  {radius = 2634, defaultdisplay = 'dec west' },
    earth = {radius = 6371, defaultdisplay = 'dms' },
    europa =  {radius = 1561, defaultdisplay = 'dec east' },
    hyperion =  {radius = 140, defaultdisplay = 'dec west' },
    iapetus =  {radius = 725, defaultdisplay = 'dec west' },
    ['io'] =  {radius = 1322, defaultdisplay = 'dec west' },
    jupiter =  {radius = 68911, defaultdisplay = 'dec east' },
    mars =  {radius = 3389.5, defaultdisplay = 'dec east' },
    mercury =  {radius = 2439.7, defaultdisplay = 'dec west' },
    mimas =  {radius = 197, defaultdisplay = 'dec west' },
    miranda =  {radius = 335, defaultdisplay = 'dec east' },
    moon =  {radius = 1736, defaultdisplay = 'dec' },
    neptune =  {radius = 24553, defaultdisplay = 'dec east' },
    oberon =  {radius = 761, defaultdisplay = 'dec east' },
    phoebe =  {radius = 110, defaultdisplay = 'dec west' },
    phobos =  {radius = 11, defaultdisplay = 'dec west' },
    pluto =  {radius = 1185, defaultdisplay = 'dec east' },
    rhea =  {radius = 765, defaultdisplay = 'dec west' },
    saturn =  {radius = 58232, defaultdisplay = 'dec east' },
    titan =  {radius = 2575.5, defaultdisplay = 'dec west' },
    tethys =  {radius = 530, defaultdisplay = 'dec west' },
    titania =  {radius = 394, defaultdisplay = 'dec east' },
    triton = {radius = 1353, defaultdisplay = 'dec east' },
    umbriel =  {radius = 584, defaultdisplay = 'dec east' },
    uranus =  {radius = 25266, defaultdisplay = 'dec east' },
    venus =  {radius = 6051.8, defaultdisplay = 'dec east' },
    vesta =  {radius = 260, defaultdisplay = 'dec east' }
}
globedata[''] = globedata.earth

local wikidatathreshold = 10 -- Se a distância entre as coordenadas Wikipedia e Wikidata exceder o limite (em quilômetros), uma categoria de manutenção será adicionada
local lang = mw.language.getContentLanguage()
local default_zoom = 13

local function makecat(cat, sortkey)
    if type( sortkey ) == 'string' then
        return '[[Category:' .. cat .. '|' .. sortkey .. ']]'
    else
        return '[[Category:' .. cat .. ']]'
    end
end

----------------------------------------
--Error handling
    --[[ Notes:
    when errors occure a new error message is concatenated to errorstring
    an error message contains an error category with a sortkey
    For major errors, it can also display an error message (the error message will the usually be returned and the function terminated)
    More minor errors do only add a category, so that readers are not bothered with error texts
    sortkeys:
        * A: invalid latitude, longitude or direction
        * B: invalid globe
        * C: something wrong with other parameters
        * D: more than one primary coord
    ]]--

local errorstring = ''

local function makeerror(args)
    local errormessage = ''
    if args.message then
        errormessage = '<strong class="error"> Coordenadas : ' .. args.message .. '</strong>'
    end
    local errorcat = ''
    if mw.title.getCurrentTitle().namespace == 0 then
        errorcat = makecat(i18n.errorcat, args.sortkey)
    end
    errorstring = errormessage .. errorcat -- reinitializes the string to avoid absurdly long messages
    return nil
end

local function showerrors()
    return errorstring
end



-- Distance computation
function p._distance(a, b, globe) -- calcula a [[distância orthodromique]] em quilóimetros entre dois pontos do globo

    globe = string.lower(globe or 'earth')
  
    -- check arguments and converts degreees to radians
    local latA, latB, longA, longB = a.latitude, b.latitude, a.longitude, b.longitude
    if (not latA) or (not latB) or (not longA) or (not longB) then return
        error('coordenadas não informadas, não consegue calcular a distância')
    end
    if type(latA) ~= 'number' or type(latB) ~= 'number' or type(longA) ~= 'number' or type(longB) ~= 'number' then
        error('coordenadas não são numéricas, não consegue calcular a distância')
    end
        if not globe or not globedata[globe] then
        return error('globe: ' .. globe .. 'não é suportado')
    end
  
    -- calcular a distância angular em radians
    local convratio = math.pi / 180 -- converter em radians
    latA, latB, longA, longB = convratio * latA, convratio * latB, convratio * longA, convratio * longB
    local cosangle = math.sin(latA) * math.sin(latB) + math.cos(latA) * math.cos(latB) * math.cos(longB - longA)
    if cosangle >= 1 then -- may be above one because of rounding errors
        return 0
    end
    local angle = math.acos(cosangle)
    -- calcular a distância em km
    local radius = globedata[globe].radius
    return radius * angle
end

function p.distance(frame)
    local args = frame.args
    return p._distance(
        {latitude = tonumber(args.latitude1), longitude = tonumber(args.longitude1)},
        {latitude = tonumber(args.latitude2), longitude = tonumber(args.longitude2)},
        args.globe)
end

local function geoHackUrl(decLat, decLong, globe, displayformat, objectname, extraparams)
    extraparams = extraparams or ''
    local geohacklatitude, geohacklongitude
    -- format latitude and longitude for the URL
    if tonumber(decLat) < 0 then
        geohacklatitude = tostring(-tonumber(decLat)) .. '_S'
    else
        geohacklatitude = decLat .. '_N'
    end
    if tonumber(decLong) < 0  then
        geohacklongitude = tostring(-tonumber(decLong)) .. '_W'
    elseif globedata[globe].defaultdisplay == 'dec west' then
        geohacklongitude = decLong .. '_W'
    else
        geohacklongitude = decLong .. '_E'
    end
    -- prepares the 'paramss=' parameter
    local geohackparams = geohacklatitude .. '_' .. geohacklongitude .. '_' ..extraparams
    -- concatenate parameteres for geohack
    return i18n.geohackurl ..
        "&pagename=" .. mw.uri.encode(mw.title.getCurrentTitle().prefixedText, "WIKI") ..
        "&params=" .. geohackparams ..
        (objectname and ("&title=" .. mw.uri.encode(objectname)) or "")
end

--HTML builder for a geohack link
local function buildHTML(decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
    -- geohack url
    local url = geoHackUrl(decLat, decLong, globe, displayformat, objectname, extraparams)
  
    -- displayed coordinates
    local displaycoords
    if string.sub(displayformat,1,3) == 'dec' then
        displaycoords = p.displaydec(decLat, decLong, displayformat)
    else
        displaycoords = {
            p.displaydmsdimension(dmsLat, displayformat),
            p.displaydmsdimension(dmsLong, displayformat),
        }
    end
  
    -- build coordinate in h-geo / h-card microformat
    local globeNode
    if globe and globe ~= 'earth' then
        globeNode = mw.html.create('data')
            :addClass('p-globe')
            :attr{ value = globe }
            :done()
    end
  
    local coordNode = mw.html.create('')
    if objectname then
        coordNode = mw.html.create('span')
            :addClass('h-card')
            :tag('data')
                :addClass('p-name')
                :attr{ value = objectname }
                :done()
    end
    coordNode
        :tag('span')
            :addClass('h-geo')
            :addClass('geo-' .. string.sub(displayformat,1,3))
            :tag('data')
                :addClass('p-latitude')
                :attr{ value = decLat }
                :wikitext( displaycoords[1] )
                :done()
            :wikitext(", ")
            :tag('data')
                :addClass('p-longitude')
                :attr{ value = decLong }
                :wikitext( displaycoords[2] )
                :done()
            :node( globeNode )
            :done()
  
    -- buid GeoHack link
    local root = mw.html.create('span')
        :addClass('plainlinks nourlexpansion')
        :attr('title', i18n.tooltip)
        :wikitext('[' .. url )
        :node(coordNode)
        :wikitext("]")
        :done()
  
    -- format result depending on args["display"] (nil, "inline", "title", "inline,title")
    local inlineText = displayinline and tostring(root) or ''
    local titleText = ''
    if displaytitle then
        local htmlTitle = mw.html.create('span')
            :attr{ id = 'coordinates' }
            :addClass( displayinline and 'noprint' or nil )
            :node( root )
        local frame = mw.getCurrentFrame()
        titleText = frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' }    )
    end
  
    return inlineText .. titleText
end

local function zoom( extraparams )
    local zoomParam = extraparams:match( '%f[%w]zoom: ?(%d+)' )
    if zoomParam then
        return zoomParam
    end
  
    local scale = extraparams:match( '%f[%w]scale: ?(%d+)' )
    if scale then
        return math.floor(math.log10( 1 / tonumber( scale ) ) * 3 + 25)
    end
  
    local extraType = extraparams:match( '%f[%w]type: ?(%w+)' )
    if extraType then
        local zoomType = {
            country = 5,
            state = 6,
            adm1st = 7,
            adm2nd = 8,
            city = 9,
            isle = 10,
            mountain = 10,
            waterbody = 10,
            airport = 12,
            landmark = 13,
        }
        return zoomType[ extraType ]
    end
end

--HTML builder for a geohack link
local function buildMaplinkHTML( decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams )
    -- displayed coordinates
    local displaycoords
    if string.sub(displayformat,1,3) == 'dec' then
        displaycoords = p.displaydec(decLat, decLong, displayformat)
    else
        displaycoords = {
            p.displaydmsdimension(dmsLat, displayformat),
            p.displaydmsdimension(dmsLong, displayformat),
        }
    end
  
    -- JSON for maplink
    local jsonParams = {
        type = 'Feature',
        geometry = {
            type ='Point',
            coordinates = {
                math_mod._round( decLong, 6 ), -- max precision in GeoJSON format
                math_mod._round( decLat, 6 )
            }
        },
        properties = {
            ['marker-color'] = "228b22",
        }
    }
    if objectname then
        jsonParams.properties.title = objectname
    end
    -- adicionar a geoshape via externaldata
    local geoshape = extraparams:match( '%f[%w]geoshape: ?(Q%d+)' )
    if not geoshape and displaytitle and mw.wikibase.getEntity() then
        geoshape = mw.wikibase.getEntity().id
    end
    if geoshape then
        jsonParams = {
            jsonParams,
            {
                type = 'ExternalData',
                service = 'geoshape',
                ids = geoshape,
                properties = {
                    ['fill-opacity'] = 0.2
                }
            }
        }
    end

    local maplink = mw.getCurrentFrame():extensionTag{
        name = 'maplink',
        content = mw.text.jsonEncode( jsonParams ),
        args = {
            text = displaycoords[1] .. ", " .. displaycoords[2],
            zoom = zoom( extraparams ) or default_zoom,
            latitude = decLat,
            longitude = decLong,
        }
    }
  
    -- format result depending on args["display"] (nil, "inline", "title", "inline,title")
    local inlineText = displayinline and maplink or ''
    local titleText = ''
    if displaytitle then
        local htmlTitle = mw.html.create('span')
            :attr{ id = 'coordinates' }
            :addClass( displayinline and 'noprint' or nil )
            :wikitext( maplink )
        local frame = mw.getCurrentFrame()
        titleText = frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' }    )
    end
  
    return inlineText .. titleText
end

-- dms specific funcions

local function twoDigit( value )
    if ( value < 10 ) then
        value = '0' .. lang:formatNum( value )
    else
        value = lang:formatNum( value )
    end
    return value
end

function p.displaydmsdimension(valuetable, format) -- formato em latitude ou longitude dms
    local str = ''
    local direction = valuetable.direction
    local degrees, minutes, seconds = '', '', ''
    local dimension

    if format == 'dms long' then
        direction = i18n[direction .. 'long']
    else
        direction = i18n[direction]
    end
    degrees = lang:formatNum( valuetable.degrees ) .. i18n.degrees
  
    if valuetable.minutes then
        minutes = twoDigit( valuetable.minutes ) .. i18n.minutes
    end
    if valuetable.seconds then
        seconds = twoDigit( valuetable.seconds ) .. i18n.seconds
    end
    return degrees .. minutes .. seconds .. direction
end

local function validdms(coordtable)
    local direction = coordtable.direction
    local degrees = coordtable.degrees or 0
    local minutes = coordtable.minutes or 0
    local seconds = coordtable.seconds or 0
    local dimension = coordtable.dimension
    if not dimension then
        if direction == 'N' or direction == 'S' then
            dimension = 'latitude'
        elseif direction == 'E' or direction == 'W' then
            dimension = 'longitude'
        else
            makeerror({message = i18n.invalidNSEW, sortkey = 'A'})
            return false
        end
    end

    if type(degrees) ~= 'number' or type(minutes) ~= 'number' or type(seconds) ~= 'number' then
        makeerror({message = i18n.invalidFormat, sortkey = 'A'})
        return false
    end
  
    if dimension == 'latitude' and direction ~= 'N' and direction ~= 'S' then
        makeerror({message = i18n.invalidNS, sortkey = 'A'})
        return false
    end
    if dimension == 'longitude' and direction ~= 'W' and direction ~= 'E' then
        makeerror({message = i18n.invalidEW, sortkey = 'A'})
        return false
    end
  
    if dimension == 'latitude' and degrees > 90 then
        makeerror({message = i18n.latitude90, sortkey = 'A'})
        return false
    end
  
    if dimension == 'longitude' and degrees > 360 then
        makeerror({message = i18n.longitude360, sortkey = 'A'})
        return false
    end
  
    if degrees < 0 or minutes < 0 or seconds < 0 then
        makeerror({message = i18n.negativeCoode, sortkey = 'A'})
        return false
    end
  
    if minutes > 60 or seconds > 60 then
        makeerror({message = i18n.minSec60, sortkey = 'A'})
        return false
    end  
    if (math.floor(degrees) ~= degrees and minutes ~= 0) or (math.floor(minutes) ~= minutes and seconds ~= 0) then
        makeerror({message = i18n.dmIntergers, sortkey = 'A'})
        return false
    end
    return true
end

local function builddmsdimension(degrees, minutes, seconds, direction, dimension)
    -- no error checking, done in function validdms
    local dimensionobject = {}
  
    -- direction and dimension (= latitude or longitude)
    dimensionobject.direction = direction
    if dimension then
        dimensionobject.dimension = dimension
    elseif direction == 'N' or direction == 'S' then
        dimensionobject.dimension = 'latitude'
    elseif direction == 'E' or direction == 'W' then
        dimensionobject.dimension = 'longitude'
    end
  
    -- degrees, minutes, seconds
    dimensionobject.degrees = tonumber(degrees)
    dimensionobject.minutes = tonumber(minutes)
    dimensionobject.seconds = tonumber(seconds)
    if degrees and not dimensionobject.degrees then dimensionobject.degrees = 'error' end
    if minutes and not dimensionobject.minutes then dimensionobject.minutes = 'error' end
    if seconds and not dimensionobject.seconds then dimensionobject.seconds = 'error' end
    return dimensionobject
end

function p._parsedmsstring( str, dimension ) -- pega uma sequência e dá nomes aos parâmetros
    -- output table: { latitude=, longitude = , direction =  }
    if type( str ) ~= 'string' then
        return nil
    end
    str = mw.ustring.gsub( mw.ustring.upper( str ), '%a+', coordParse )
    if not tonumber( str ) and not str:find( '/' ) and str:find( '°' ) then
        local str2 = mw.ustring.gsub( str, '[°″′\"\'\194\160 ]+', '/' )
        -- avoid cases were there is degree ans seconds but no minutes
        if not mw.ustring.find( str, '[″"]' ) or mw.ustring.find( str, '%d[′\'][ \194\160%d]' ) then
            str = str2
        end
    end
    if not tonumber(str) and not string.find(str, '/') then
        makeerror({message = i18n.invalidFormat, sortkey= 'A'})
        return nil
    end
    args = mw.text.split(str, '/', true)
    if #args > 4 then
        makeerror({message = i18n.tooManyParam, sortkey= 'A' })
    end  
    local direction = mw.text.trim(args[#args])
    table.remove(args)
    local degrees, minutes, seconds = args[1], args[2], args[3]
    local dimensionobject = builddmsdimension(degrees, minutes, seconds, direction, dimension)
    if validdms(dimensionobject) then
        return dimensionobject
    else
        return nil
    end
end

--- decimal specific functions
function p.displaydec(latitude, longitude, format)
    lat = lang:formatNum( latitude )
    long = lang:formatNum( longitude )
  
    if format == 'dec west' or  format == 'dec east' then
        local symbolNS, symbolEW = i18n.N, i18n.E
        if latitude < 0 then
            symbolNS = i18n.S
            lat = lang:formatNum( -latitude )
        end
        if format == 'dec west' then
            symbolEW = i18n.W
        end
        if longitude < 0 then
            long = lang:formatNum( 360 + longitude )
        end
      
        return { lat .. i18n.degrees .. symbolNS,  long ..  i18n.degrees .. symbolEW }
      
    else
        return { lat, long }
    end
end


local function parsedec(dec, coordtype, globe) -- coordtype = latitude or longitude
    dec = mw.text.trim(dec)
    if not dec then
        return nil
    end
    if coordtype ~= 'latitude' and coordtype ~= 'longitude' then
        makeerror({'invalid coord type', sortkey = "A"})
        return nil
    end
    local numdec = tonumber(dec) -- numeric value, kept separated as it looses significant zeros
    if not numdec then -- tries the decimal + direction format
        dec = mw.ustring.gsub( mw.ustring.upper( dec ), '%a+', coordParse )
        local direction = mw.ustring.sub(dec, mw.ustring.len(dec), mw.ustring.len(dec))
        dec = mw.ustring.sub(dec, 1, mw.ustring.len(dec)-2) -- removes the /N at the end
        if not dec or not tonumber(dec) then
            return nil
        end
        if direction == 'N' or direction == 'E' or direction == 'W' and globedata[globe].defaultdisplay == 'dec west' then
            numdec = tonumber( dec )
        elseif direction == 'W' or direction == 'S' then
            dec = '-' .. dec
            numdec = tonumber( dec )
        else
            if coordtype == 'latitude' then
                makeerror({message = i18n.invalidNS, sortkey = 'A'})
            else
                makeerror({message = i18n.invalidEW, sortkey = 'A'})
            end
            return nil
        end
    end

    if coordtype == 'latitude' and math.abs(numdec) > 90 then
        makeerror({message = i18n.latitude90 , sortkey = 'A'})
        return nil
    end
    if coordtype == 'longitude' and math.abs(numdec) > 360 then
        makeerror({message = i18n.longitude360 , sortkey = 'A'})
        return nil
    end
    return dec
end

-- dms/dec conversion functions
local function convertprecision(precision) -- converts a decimal precision like "2" into "dm"
    if precision >= 3 then
        return 'dms'
    elseif precision >=1 then
        return 'dm'
    else
        return 'd'
    end
end

local function determinedmsprec(decs) -- returns the most precision for a dec2dms conversion, depending on the most precise value in the decs table
    local precision = 0
    for d, val in ipairs(decs) do
        precision = math.max(precision, math_mod._precision(val))
    end
    return convertprecision(precision)
end

local function dec2dms_d(dec)
    local degrees = math_mod._round( dec, 0 )
    return degrees
end

local function dec2dms_dm(dec)
    dec = math_mod._round( dec * 60, 0 )
    local minutes = dec % 60
    dec = math.floor( (dec - minutes) / 60 )
    local degrees = dec % 360
    return degrees, minutes
end

local function dec2dms_dms(dec)
    dec = math_mod._round( dec * 60 * 60, 0 )
    local seconds = dec % 60
    dec = math.floor( (dec - seconds) / 60 )
    local minutes = dec % 60
    dec = math.floor( (dec - minutes) / 60 )
    local degrees = dec % 360
    return degrees, minutes, seconds
end

function p._dec2dms(dec, coordtype, precision, globe) -- coordtype: latitude or longitude
    local degrees, minutes, seconds
  
    -- verificação do globo
    if not ( globe and globedata[ globe ] ) then
        globe = 'earth'
    end
  
    -- precision
    if not precision or precision == '' then
        precision = determinedmsprec({dec})
    end
    if precision ~= 'd' and precision ~= 'dm' and precision ~= 'dms' then
        return makeerror({sortkey = 'C'})
    end
    local dec = tonumber(dec)
  
    -- direction
    local direction
    if coordtype == 'latitude' then
        if dec < 0 then
            direction = 'S'
        else
            direction = 'N'
        end
    elseif coordtype == 'longitude' then
        if dec < 0 or globedata[globe].defaultdisplay == 'dec west' then
            direction = 'W'
        else
            direction = 'E'
        end
    end
  
    -- conversion
    dec = math.abs(dec) -- as coordenadas em dms são sempre positivas
    if precision == 'dms' then
        degrees, minutes, seconds = dec2dms_dms(dec)
    elseif precision == 'dm' then
        degrees, minutes = dec2dms_dm(dec)
    else
        degrees = dec2dms_d(dec)
    end
    return builddmsdimension(degrees, minutes, seconds, direction)
end

function p.dec2dms(frame) -- legacy function somewhat cumbersome syntax
    args = frame.args
    local dec = args[1]
    if not tonumber(dec) then
        makeerror({message = i18n.invalidFormat, sortkey = 'A'})
        return showerrors()
    end
    local dirpositive = string.lower(args[2] or '')
    local dirnegative = string.lower(args[3] or '')
    local precision = string.lower(args[4] or '')
    local displayformat, coordtype
  
    if dirpositive == 'n' or dirpositive == 'norte' then
        coordtype = 'latitude'
    else
        coordtype = 'longitude'
    end
    if dirpositive == 'norte' or dirpositive == 'leste' or dirnegative == 'oeste' or dirnegative == 'sul' then
        displayformat = 'dms long'
    end
    local coordobject = p._dec2dms(dec, coordtype, precision)
    if coordobject then
        return p.displaydmsdimension(coordobject, displayformat) .. showerrors()
    else
        return showerrors()
    end
end


function p._dms2dec(dmsobject) -- transformar uma tabela de minutos de segundo em um número decimal
    local direction, degrees, minutes, seconds = dmsobject.direction, dmsobject.degrees, dmsobject.minutes, dmsobject.seconds
    local factor = 0
    local precision = 0
    if not minutes then minutes = 0 end
    if not seconds then seconds = 0 end
  
    if direction == "N" or direction == "E" then
        factor = 1
    elseif direction == "W" or direction == "S" then
        factor = -1
    elseif not direction then
        makeerror({message = i18n.noCardinalDirection, sortkey = 'A'})
        return nil
    else
        makeerror({message = i18n.invalidDirection, sortkey = 'A'})
        return nil
    end
  
    if dmsobject.seconds then -- verifique a precisão dos dados iniciais
        precision = 5 + math.max( math_mod._precision(tostring(seconds), 0 ) ) -- passagem por cadeias de texto bastante complicada?
    elseif dmsobject.minutes then
        precision = 3 + math.max( math_mod._precision(tostring(minutes), 0 ) )
    else
        precision = math.max( math_mod._precision(tostring(degrees), 0 ) )
    end
  
    local decimal = factor * (degrees+(minutes+seconds/60)/60)
    return math_mod._round(decimal, precision)
end

function p.dms2dec(frame) -- legacy function, somewhat bizarre syntax
    local args = frame.args
    if tonumber(args[1]) then
        return args[1] -- coordenadas já em decimal
    elseif not args[2] then
        local dmsobject = p._parsedmsstring(args[1])
        if dmsobject then
            return p._dms2dec(dmsobject) -- coordena sob a proa 23/22/N
        else
            local coordType
            if args[1]:match( '[NS]' ) then
                coordType = 'latitude'
            elseif args[1]:match( '[EWO]') then
                coordType = 'longitude'
            end
            if coordType then
                local result = parsedec( args[1],  coordType, args.globe or 'earth' )
                if result then
                    return result
                end
            end
            return showerrors()
        end
    else
        return p._dms2dec({direction = args[1], degrees = tonumber(args[2]), minutes = tonumber(args[3]), seconds = tonumber(args[4])})
    end
end

-- Wikidata
local function convertwikidataprecision(precision) -- converts a decima like "0.1" into "dm"
    if precision < 0.016 then
        return 'dms'
    elseif precision < 1 then
        return 'dm'
    else
        return 'd'
    end
end

local function wikidatacoords(query)
    query = query or {property = 'p625'}
    query.formatting = 'raw'
    local wd = require('Módulo:Infobox/Wikidata')
    local claim = wd.getClaims(query)
    if claim and claim[1] then -- redundant but more robust in case of a change in the code of Module:Infobox/Wikidata
        local coords = wd.formatSnak(claim[1].mainsnak) -- todo: check for special values
        -- Wikidata does not handle correctly +West longitudes
        if globedata[ coords.globe ] and globedata[ coords.globe ].defaultdisplay == 'dec west' then
            coords.longitude = math.abs( coords.longitude )
        end
        return coords.latitude, coords.longitude, coords.globe or 'earth', convertwikidataprecision(coords.precision or .001)
    end
    return nil
end


local function wikidatacat(globe)
    local entitycat = mw.wikibase.getEntity()
  
    local basecat = '!Páginas com mapas'
    local finalcat = {}
    --BADGES
    if entitycat then
        --BADGES
           for i, badgeId in ipairs( entitycat.sitelinks['ptwiki'].badges ) do
            if badgeId == 'Q17437796'  then
                basecat= string.gsub(basecat, "!Páginas com mapas", "!Artigos por qualidade sobre geografia")
            end
            if badgeId == 'Q17437798'  then
                basecat= string.gsub(basecat, "!Páginas com mapas", "!Artigos bons sobre geografia")
            end
        end
    end
        table.insert(finalcat,basecat)
  
    return finalcat
end

 -- main function for displaying coordinates
function p._coord(args)

    -- I declare variable  
    local displayformat = args.format -- string: one of: 'dms', 'dms long', 'dec', 'dec east' and 'dec west'
    local displayplace = string.lower(args.display or 'inline') --string: one of 'inline', 'title' or 'inline,title'
    local objectname = (args.name ~= '') and args.name -- string: name of the title displayed in geohack
    local notes = (' ' and args.notes) or '' -- string: notes to de displayed after coordinates
    local wikidata = args.wikidata -- string: set to "true" if needed
    local wikidataquery = args.wikidataquery -- table: see [[Module:Wikidata]] see function wikidatacoords
    local dmslatitude, dmslongitude -- table (when created)  
    local extraparams = args.extraparams or '' -- string (legacy, corresponds to geohackparams)
     local trackingstring = '' -- tracking cats except error cats (already in errorstring)
     local rawlat, rawlong = args.latitude, args.longitude
     if rawlat == '' then rawlat = nil end
     if rawlong == '' then rawlong = nil end
     local globe = string.lower( args.globe or extraparams:match('globe:(%a+)') or '' ) -- string: see the globedata table for accepted values
    local latitude, longitude, precision, dmslatitude, dmslongitude -- latitude and longitude in decimal / dmslatitude and dmslongitude: tables withdms coords
    local maplink = true -- use maplink whenever it is possible
  
    -- II extract coordinates from Wikitext
    if (rawlat or rawlong) then
        if (not rawlat) or (not rawlong) then -- if latitude is provided so should be longitude
            makeerror({message = i18n.coordMissing, sortkey = 'A'})
            return showerrors()
        end
        latitude = parsedec(rawlat, 'latitude', globe)

        if latitude then -- if latitude is decimal
            longitude = parsedec(rawlong, 'longitude', globe) -- so should be longitude
            precision = determinedmsprec({latitude, longitude}) -- before conversion from string to number for trailing zeros
            if not latitude or not longitude then
                if errorstring == '' then
                    makeerror({message = i18n.invalidFormat, sortkey = 'A'})
                end
                return showerrors()
            end
            dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision, globe)
            latitude, longitude = tonumber(latitude), tonumber(longitude)
        else -- if latitude is not decimal try to parse it as a dms string
            dmslatitude, dmslongitude = p._parsedmsstring(args.latitude, 'latitude'), p._parsedmsstring(args.longitude, 'longitude')
            if not dmslatitude or not dmslongitude then
                return showerrors()
            end
            latitude, longitude = p._dms2dec(dmslatitude), p._dms2dec(dmslongitude)
        end
    end

    -- III extract coordinate data from Wikidata and compare them to local data
    local wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision
    if wikidata == 'true' then
        wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision = wikidatacoords(wikidataquery)
      
        if wikidatalatitude and latitude and longitude then
            local maxdistance = tonumber(args.maxdistance) or wikidatathreshold
            if p._distance({latitude = latitude, longitude= longitude}, {latitude = wikidatalatitude, longitude= wikidatalongitude}, wikidataglobe) <  maxdistance then
                trackingstring = trackingstring .. makecat(i18n.sameaswikidata)
                    else
                trackingstring = trackingstring .. makecat(i18n.notaswikidata)
            end
        end
        if wikidatalatitude and not latitude then
            latitude, longitude, globe, precision = wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision
            dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision, globe)
            trackingstring = trackingstring .. makecat(i18n.throughwikidata)
        end
  
        if latitude and not wikidatalatitude then
            if mw.title.getCurrentTitle().namespace == 0 then
                trackingstring = trackingstring .. makecat(i18n.nowikidata)
            end
        end
    end


    -- exit if stil no latitude or no longitude
    if not latitude and not longitude then
        return nil -- não adicione nada aqui para que a chamada para esta função retorne nil na ausência de dados
    end

    -- IV best guesses for missing parameters
  
    --- globe
    if globe == '' then
        globe = 'earth'
    end
    if not globedata[globe] then
        makeerror({message = i18n.invalidGlobe .. globe})
        globe = 'earth'
    end
    if globe ~= 'earth' then
        extraparams = extraparams .. '_globe:' .. globe -- não há problema se o globo é duplicado
        maplink = false
    end
  
    --- diplayformat
    if not displayformat or displayformat == '' then
        displayformat = globedata[globe].defaultdisplay
    end
  
    -- displayinline/displaytitle
    local displayinline =  string.find(displayplace, 'inline')
    local displaytitle = string.find(displayplace, 'title')
    if not displayinline and not displaytitle then
        displayinline = true
        if displayplace ~= '' then
            makeerror({sortkey = 'C'}) --error if display not empty, but not not a major error, continue
        end
    end
    if displaytitle and mw.title.getCurrentTitle().namespace == 0 then
        --local cattoappend=globedata[globe].trackingcat
        --Recuperação dos badges
        local cats=wikidatacat(globe)
        for i, cat in ipairs( cats ) do
            trackingstring = trackingstring .. makecat(cat)
        end
  
    end
  
-- V geodata
    local geodata = ''
    if latitude and longitude then
        local latstring, longstring = tostring(latitude), tostring(longitude)
        local primary = ''

        local frame = mw.getCurrentFrame()
        local geodataparams = {[1] = latstring, [2] = longstring, [3] = extraparams }
        if displaytitle then
            geodataparams[4] = 'primary'
        end
        if objectname then
            geodataparams.name = objectname
        end
        geodata = frame:callParserFunction('#coordinates', geodataparams )
        if string.find(geodata, 'error') then -- the only error that has not been caught yet is primary key
            geodata = ''
            makeerror({sortkey='D'})
        end
    end
-- VI final output
    local mainstring = ''
    if maplink then
        mainstring = buildMaplinkHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname,extraparams )
    else
        mainstring = buildHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname,extraparams )
    end
  
    return mainstring .. notes .. trackingstring .. geodata .. showerrors()
end

function p.coord(frame) -- parses the strange parameters of Template:Coord before sending them to p.coord
    local args = frame.args
    local numericargs = {}
    for i, j in ipairs(args) do
        args[i] = mw.text.trim(j)
        if type(i) == 'number' and args[i] ~= '' then
            table.insert(numericargs, args[i])
        end
    end

    if #numericargs %2 == 1 then -- if the number of args is odd, the last one provides formatting parameters
        args.extraparams = numericargs[#numericargs]
        if #numericargs == 1 and tonumber(numericargs[1]) then
            makeerror({message = i18n.coordMissing, sortkey = 'A'})
            return showerrors()
        end
        table.remove(numericargs)
    end
    for i, j in ipairs(numericargs) do
        if i <= (#numericargs / 2) then
            if not args.latitude then
                args.latitude = j
            else
                args.latitude =    args.latitude .. '/' .. j
            end
        else
            if not args.longitude then
                args.longitude = j
            else
                args.longitude = args.longitude .. '/' .. j
            end
        end
    end

    if string.find(args.latitude or '', 'E') or string.find(args.latitude or '', 'W') then
        args.latitude, args.longitude = args.longitude, args.latitude
    end
    return p._coord(args)
end

function p.Coord(frame)
    return p.coord(frame)
end

function p.latitude(frame) -- helper function para infobox, a depreciar
    local args = frame.args
    local latitude  = frame.args[1]
    if latitude and mw.text.trim(latitude) ~= '' then
        return latitude
    elseif frame.args['wikidata'] == 'true' then
        local lat, long = wikidatacoords()
        return lat
    end
end
function p.longitude(frame) -- helper function para infobox, a depreciar
    local args = frame.args
    local longitude = frame.args[1]
    if longitude and mw.text.trim(longitude) ~= '' then
        return longitude
    elseif frame.args['wikidata'] == 'true' then
        local lat, long = wikidatacoords()
        return long
    end
end

--[[
coord2text

(documentação a traduzir)
]]
	function p.coord2text(frame)
	if frame.args[1] == '' or frame.args[2] == '' or not frame.args[2] then return nil end
	frame.args[2] = mw.text.trim(frame.args[2])
	if frame.args[2] == 'lat' or frame.args[2] == 'long' then
		local result, negative = mw.text.split((mw.ustring.match(frame.args[1],'[%.%d]+°[NS] [%.%d]+°[EW]') or ''), ' ')
		if frame.args[2] == 'lat' then
			result, negative = result[1], 'S'
		else
			result, negative = result[2], 'W'
		end
		result = mw.text.split(result, '°')
		if result[2] == negative then result[1] = '-'..result[1] end
		return result[1]
	else
		return mw.ustring.match(frame.args[1], 'params=.-_'..frame.args[2]..':(.-)[ _]')
	end
end
--[[
link

Simple function to export the coordinates link for other uses.

Usage:
    {{#invoke:Coordenadas | link }}
 
]]
function p.link(frame)
    return coord_link;
end

return p