local p = {}

-- Get the page object. This will return the page object for the page
-- specified, or nil if there are errors in the title, if the
-- expensive function count has been exceeded, or if the page was not
-- specified.
function getPageObject( page )
    if not page then
        return nil
    end
    -- Get the page object, passing the function through pcall 
    -- in case we are over the expensive function count limit.
    local noError, pageObject = pcall(mw.title.new, page)
    if not noError then
        return nil
    else
        return pageObject
    end
end

-- Process the separator parameter.
local function getSeparator( sep )
    if sep and type(sep) == 'string' then
        if sep == 'dot' 
            or sep =='pipe'
            or sep == 'comma'
            or sep == 'tpt-languages' then
            return mw.message.new( sep .. '-separator' ):plain()
        else
            return sep
        end
    else
        return nil
    end
end

local function generateLink( page, nspace, delim, edelim )
    if not page then
        return nil
    end
    local pagename = getPageObject( page )
    if not pagename then
        -- Default to the args we were passed if our page
        -- object was nil.
        pagename = page
    else
        pagename = pagename.text
    end
    delim = delim or ''
    edelim = edelim or delim
    nspace = nspace or mw.title.getCurrentTitle().nsText
    return mw.ustring.format( 
        '%s[[:%s:%s|%s]]%s',
        delim, nspace, pagename, page, edelim
    )
end

local function _main( args )
    local t = {}
    local separator = getSeparator( args.separator )
    local conjunction = getSeparator( args.conjunction )
    for i, v in ipairs( args ) do
        table.insert( t, generateLink(
            v, args.nspace, args.delim, args.edelim
        ) )
    end
    return mw.text.listToText( t, separator, conjunction )
end

function p.main( frame )
    local origArgs
    if frame == mw.getCurrentFrame() then
        -- We're being called via #invoke. If the invoking template passed any arguments,
        -- use them. Otherwise, use the arguments that were passed into the template.
        origArgs = frame:getParent().args
        for k, v in pairs( frame.args ) do
            origArgs = frame.args
            break
        end
    else        
        -- We're being called from another module or from the debug console, so assume
        -- the arguments are passed in directly.
        origArgs = frame
    end
    
    -- Process integer args. Allow for explicit positional arguments that are
    -- specified out of order, e.g. {{br separated entries|3=entry3}}.
    -- After processing, the args can be accessed accurately from ipairs.
    local args = {}
    for k, v in pairs( origArgs ) do
        if type( k ) == 'number' and 
            k >= 1 and
            math.floor( k ) == k and
            mw.ustring.match( v, '%S' ) then -- Remove blank or whitespace values.
            table.insert( args, k )
        end
    end
    table.sort( args )
    for i, v in ipairs( args ) do
        args[ i ] = origArgs[ v ]
        -- Trim whitespace.
        if type( args[ i ] ) == 'string' then
            args[ i ] = mw.text.trim( args[ i ] )
        end
    end
    
    -- Get old named args. We don't need to remove blank values
    -- as for the nspace and edelim parameters the behaviour is different
    -- depending on whether the parameters are blank or absent, and for
    -- the delim parameter the default should be the blank string anyway.
    args.delim = origArgs.delim
    args.edelim = origArgs.edelim
    args.nspace = origArgs.nspace
    
    -- Get new named args, "separator" and "conjunction", and strip blank values.
    if origArgs.separator and origArgs.separator ~= '' then
        args.separator = origArgs.separator
    end
    if origArgs.conjunction and origArgs.conjunction ~= '' then
        args.conjunction = origArgs.conjunction
    end
 
    return _main( args )
end
 
return p