}}


local yesno = require('Module:Yesno')

local mExtra -- [[Module:UserLinks/extra]]
local mArguments -- [[Module:Arguments]]
local mToolbar -- [[Module:Toolbar]]
local mCategoryHandler -- [[Module:Category handler]]
local mTableTools -- [[Module:TableTools]]
local interwikiTable -- [[Module:InterwikiTable]]

local mShared = require('Module:UserLinks/shared')
local raiseError = mShared.raiseError
local maybeLoadModule = mShared.maybeLoadModule
local makeWikitextError = mShared.makeWikitextError
local makeWikilink = mShared.makeWikilink
local makeUrlLink = mShared.makeUrlLink
local makeFullUrlLink = mShared.makeFullUrlLink
local message = mShared.message

local p = {}

--------------------------------------------------------------------------------
-- Link table
--------------------------------------------------------------------------------

function p.getLinks(snippets)

	local links, linkFunctions = {}, {}

	----------------------------------------------------------------------------
	-- Link functions
	----------------------------------------------------------------------------

	function linkFunctions.u(snippets)
		-- User page
		return makeWikilink(
			snippets.interwiki,
			2,
			snippets.username,
			snippets.username
		)
	end
	
	function linkFunctions.np(snippets)
		-- User page (no ping)
		return '<span class="plainlinks">' .. makeFullUrlLink(
			snippets.interwiki,
			2,
			snippets.username,
			'',
			snippets.username
		) .. '</span>'
	end

	function linkFunctions.t(snippets)
		-- User talk page
		return makeWikilink(
			snippets.interwiki,
			3,
			snippets.username,
			message('display-talk')
		)
	end

	function linkFunctions.c(snippets)
		-- Contributions
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Contribs/' .. snippets.username,
			message('display-contributions')
		)
	end
	
	function linkFunctions.c64(snippets)
		-- Contributions
		local first64 = snippets.username:match('^%x+:%x+:%x+:%x+:')
			or snippets.username:match('^%x+:%x+:%x+:')
			or snippets.username:match('^%x+:%x+:')
			or snippets.username:match('^%x+:')
		return first64 and makeWikilink(
			snippets.interwiki,
			-1,
			'Contribs/' .. first64 .. ':/64',
			'(/64)'
		) or ''
	end

	function linkFunctions.ct(snippets)
		-- Edit count
		return makeUrlLink(
			{
				host = 'xtools.wmflabs.org',
				path = '/ec/',
				query = {
					username = snippets.username,
					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
				}
			},
			message('display-count')
		)
	end

	function linkFunctions.m(snippets)
		-- Page moves
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/move/' .. snippets.username,
			message('display-moves')
		)
	end

	function linkFunctions.l(snippets)
		-- Logs
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/' .. snippets.username,
			message('display-logs')
		)
	end

	function linkFunctions.ae(snippets)
		-- Automated edits (and non-automated contributions).
		return makeUrlLink(
			{
				host = 'xtools.wmflabs.org',
				path = '/autoedits/',
				query = {
					username = snippets.username,
					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
				}
			},
			message('display-autoedits')
		)
	end

	function linkFunctions.bl(snippets)
		-- Block log
		return makeFullUrlLink(
			snippets.interwiki,
			-1,
			'Log/block',
			{page = 'User:' .. snippets.username},
			message('display-blocklog')
		)
	end

	function linkFunctions.bls(snippets)
		-- Blocks
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/block/' .. snippets.username,
			message('display-blocks')
		)
	end

	function linkFunctions.bu(snippets)
		-- Block user
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Block/' .. snippets.username,
			message('display-blockuser')
		)
	end

	function linkFunctions.ca(snippets)
		-- Central auth
		return makeWikilink(
			snippets.interwiki,
			-1,
			'CentralAuth/' .. snippets.username,
			message('display-centralauth')
		)
	end

	function linkFunctions.dc(snippets)
		-- Deleted contribs
		return makeWikilink(
			snippets.interwiki,
			-1,
			'DeletedContributions/' .. snippets.username,
			message('display-deletedcontributions')
		)
	end

	function linkFunctions.e(snippets)
		-- Email
		return makeWikilink(
			snippets.interwiki,
			-1,
			'EmailUser/' .. snippets.username,
			message('display-email')
		)
	end

	function linkFunctions.es(snippets)
		-- Edit summaries
		return makeUrlLink(
			{
				host = 'xtools.wmflabs.org',
				path = '/editsummary/',
				query = {
					username = snippets.username,
					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
				}
			},
			message('display-editsummaries')
		)
	end

	function linkFunctions.del(snippets)
		-- Deletions
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/delete/' .. snippets.username,
			message('display-deletions')
		)
	end

	function linkFunctions.lu(snippets)
		-- List user
		return makeFullUrlLink(
			snippets.interwiki,
			-1,
			'ListUsers',
			{limit = 1, username = snippets.username},
			message('display-listuser')
		)
	end

	function linkFunctions.sul(snippets)
		-- SUL
		return makeWikilink(
			nil,
			nil,
			'sulutil:' .. snippets.username,
			message('display-sul')
		)
	end

	function linkFunctions.tl(snippets)
		-- Target logs
		return makeFullUrlLink(
			snippets.interwiki,
			-1,
			'Log',
			{page = mw.site.namespaces[2].name .. ':' .. snippets.username},
			message('display-targetlogs')
		)
	end

	function linkFunctions.efl(snippets)
		-- Edit filter log
		return makeFullUrlLink(
			snippets.interwiki,
			-1,
			'AbuseLog',
			{wpSearchUser = snippets.username},
			message('display-abuselog')
		)
	end

	function linkFunctions.pr(snippets)
		-- Protections
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/protect/' .. snippets.username,
			message('display-protections')
		)
	end

	function linkFunctions.rl(snippets)
		-- User rights
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/rights/' .. snippets.username,
			message('display-rights')
		)
	end

	function linkFunctions.ren(snippets)
		-- Renames
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Log/renameuser/' .. snippets.username,
			message('display-renames')
		)
	end

	function linkFunctions.rfa(snippets)
		-- Requests for adminship
		return makeWikilink(
			nil,
			-1,
			'PrefixIndex/' .. message('page-rfa') .. '/' .. snippets.username,
			message('display-rfa')
		)
	end

	function linkFunctions.api(snippets)
		-- API user data
		return makeUrlLink(
			{
				host = snippets.fullDomain,
				path = '/w/api.php',
				query = {
					action = 'query',
					list = 'users',
					usprop = 'groups|editcount',
					ususers = snippets.username
				}
			},
			message('display-api')
		)
	end

	function linkFunctions.up(snippets)
		-- Uploads
		return makeWikilink(
			snippets.interwiki,
			-1,
			'ListFiles/' .. snippets.username,
			message('display-uploads')
		)
	end
	
	function linkFunctions.nuke(snippets)
		-- Mass delete/Special:Nuke
		return makeWikilink(
			snippets.interwiki,
			-1,
			'Nuke/' .. snippets.username,
			message('display-nuke')

		)
	end
	
	----------------------------------------------------------------------------
	-- End of link functions
	----------------------------------------------------------------------------

	-- Define the metatable that memoizes the link functions, and fetches link
	-- functions from [[Module:UserLinks/extra]] if necessary.

	-- Lazily initialise the extraLinkFunctions table. We only want to load
	-- [[Module:UserLinks/extra]] as necessary, so it has a low transclusion
	-- count.
	local extraLinkFunctions

	-- Define functions for shared code in the metatable.
	local function validateCode(code)
		-- Checks whether code is a valid link code - i.e. checks that it is a
		-- string and that it is not the blank string. Returns the code if
		-- the check passes, and nil if not.
		if type(code) == 'string' and code ~= '' then
			return code
		else
			return nil
		end
	end

	local function getExtraLinkFunctions()
		-- Loads the table of extra link functions from the /extra module.
		-- If there is a problem with loading it, return false. We use the
		-- distinction between false and nil to record whether we have already
		-- tried to load it.
		if extraLinkFunctions ~= nil then
			return extraLinkFunctions
		end
		if mExtra == nil then
			-- If loading the module fails, maybeLoadModule returns false.
			-- Here we use the distinction between false and nil to record
			-- whether we have already tried to load the /extra module.
			mExtra = maybeLoadModule('Module:UserLinks/extra')
		end
		if type(mExtra) == 'table'
			and type(mExtra.linkFunctions) == 'table'
		then
			extraLinkFunctions = mExtra.linkFunctions
		else
			extraLinkFunctions = false
		end
		return extraLinkFunctions
	end

	local function memoizeExtraLink(code, func)
		local success, link = pcall(func, snippets)
		if success and type(link) == 'string' then
			links[code] = link
			return link
		end
		return nil
	end

	-- Define the metatable.
	setmetatable(links, {
		__index = function (t, key)
			local code = validateCode(key)
			if not code then
				raiseError(
					message('error-malformedlinkcode'),
					message('error-malformedlinkcode-section')
				)
			end
			local linkFunction = linkFunctions[code]
			local link
			if linkFunction then
				link = linkFunction(snippets)
				links[code] = link
			else
				extraLinkFunctions = getExtraLinkFunctions()
				if extraLinkFunctions then
					local extraLinkFunction = extraLinkFunctions[code]
					if type(extraLinkFunction) == 'function' then
						link = memoizeExtraLink(code, extraLinkFunction)
					end
				end
			end
			if link then
				return link
			else
				raiseError(
					message('error-invalidlinkcode', code),
					message('error-invalidlinkcode-section')
				)
			end
		end,
		__pairs = function ()
			extraLinkFunctions = getExtraLinkFunctions()
			if extraLinkFunctions then
				for code, func in pairs(extraLinkFunctions) do
					if validateCode(code) and type(func) == 'function' then
						memoizeExtraLink(code, func)
					end
				end
			end
			-- Allow built-in functions to overwrite extra functions.
			for code, func in pairs(linkFunctions) do
				local link = func(snippets)
				links[code] = link
			end
			return function (t, key)
				return next(links, key)
			end
		end
	})
	return links
end

--------------------------------------------------------------------------------
-- User data snippets
--------------------------------------------------------------------------------

function p.getSnippets(args)

	local snippets, snippetFunctions = {}, {}
	setmetatable(snippets, {
		__index = function (t, key)
			local snippetFunction = snippetFunctions[key]
			if snippetFunction then
				snippets[key] = snippetFunction() or ''
				return snippets[key]
			else
				raiseError(
					message('error-nosnippet', key),
					message('error-nosnippet-section')
				)
			end
		end
	})

	
	local function snippetExists(key)
		
		return snippets[key] ~= ''
	end

	local function getSnippet(key)
		local ret = snippets[key]
		if ret == '' then
			return nil
		else
			return ret
		end
	end


	function snippetFunctions.username()
		
		local username = args.user or args.User
		return username or raiseError(
			message('error-nousername'),
			message('error-nousername-section')
		)
	end

	function snippetFunctions.usernameHtml()
		
		return mw.uri.encode(snippets.username)
	end

	function snippetFunctions.project()
		
		local project = args.Project or args.project
		if not project then
			return nil
		end
		local projectValidated, interwikiTableKey = p.validateProjectCode(project)
		if not projectValidated then
			if mw.language.isKnownLanguageTag(project) then
				if not snippetExists('lang') then
					snippets.lang = project
				end
			else
				raiseError(
					message('error-invalidproject', project),
					message('error-invalidproject-section')
				)
			end
		end
		snippets.interwikiTableKey = interwikiTableKey
		return project
	end

	function snippetFunctions.interwikiTableKey()
	
		return rawget(snippets, 'interwikiTableKey')
	end

	function snippetFunctions.toolProject()
	
		local project = getSnippet('project')
		if not project then
			return message('snippet-project-default')
		else
			return project
		end
	end

	function snippetFunctions.projectLong()
		
		local key = getSnippet('interwikiTableKey')
		if not key then
			return message('snippet-projectlong-default')
		end
		interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
		local prefixes = interwikiTable[key].iw_prefix
		
		return prefixes[2] or prefixes[1] 
	end

	function snippetFunctions.lang()
		
		local lang = args.lang or args.Lang
		if not lang then
			return nil
		end
		if mw.language.isKnownLanguageTag(lang) then
			return lang
		else
			raiseError(
				message('error-invalidlanguage', lang),
				message('error-invalidlanguage-section')
			)
		end
	end

	function snippetFunctions.toolLang()
		
		return getSnippet('lang') or message('snippet-lang-default')
	end

	function snippetFunctions.interwiki()
	
		local project = getSnippet('project')
		local lang = getSnippet('lang')
		if not project and not lang then
			return nil
		end
		local ret = {}
		ret[#ret + 1] = project
		ret[#ret + 1] = lang
		return table.concat(ret, ':')
	end

	function snippetFunctions.fullDomain()
		
		local fullDomain
		local lang = getSnippet('toolLang')
		local key = getSnippet('interwikiTableKey')
		if key then
			interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
			local domain = interwikiTable[key].domain
			local takesLangPrefix = interwikiTable[key].takes_lang_prefix
			if takesLangPrefix then
				fullDomain = lang .. '.' .. domain
			else
				fullDomain = domain
			end
		else
			fullDomain = lang .. '.wikipedia.org'
		end
		return fullDomain
	end

	

	return snippets
end 

function p.validateProjectCode(s)

	interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
	for key, t in pairs(interwikiTable) do
		for i, prefix in ipairs(t.iw_prefix) do
			if s == prefix then
				return s, key
			end
		end
	end
	return nil, nil
end

--------------------------------------------------------------------------------
-- Main functions
--------------------------------------------------------------------------------

local function makeInvokeFunction(funcName)

	return function (frame)
		mArguments = require('Module:Arguments')
		local args = mArguments.getArgs(frame)
		return p[funcName](args)
	end
end

p.main = makeInvokeFunction('_main')

function p._main(args)

	local options = p.getOptions(args)
	local snippets = p.getSnippets(args)
	local codes = p.getCodes(args)
	local links = p.getLinks(snippets)
	
	local success, result = pcall(p.export, codes, links, options)
	if success then
		return result
	else
		return makeWikitextError(result, options.isDemo)
	end
end

function p.getOptions(args)
	
	local options = {}
	options.isDemo = yesno(args.demo) or false
	options.noPing = yesno(args.noPing) or yesno(args.noping) or yesno(args.np) or false
	options.toolbarStyle = yesno(args.small) and 'font-size: 90%;' or nil
	options.sup = yesno(args.sup, true)
	options.separator = args.separator
	options.span = args.span
	return options
end

function p.getCodes(args)
	
	mTableTools = maybeLoadModule('Module:TableTools')
	local codes
	if mTableTools then
		codes = mTableTools.compressSparseArray(args)
	else
		codes = {}
		for i, code in ipairs(args) do
			codes[i] = code
		end
	end
	return codes
end

function p.export(codes, links, options)
	
	local userLink = options.noPing and links.np or links.u

	
	if #codes < 1 then
		return userLink
	end

	
	mToolbar = require('Module:Toolbar')
	local toolbarArgs = {}
	for i, code in ipairs(codes) do
		local link = links[code]
		toolbarArgs[#toolbarArgs + 1] = link
	end
	toolbarArgs.style = options.toolbarStyle
	toolbarArgs.separator = options.separator or 'dot'
	toolbarArgs.span = options.span
	local toolbar = mToolbar.main(toolbarArgs)

	
	if options.sup then
		toolbar = '<sup>' .. toolbar .. '</sup>'
	end
	
	
	local space = mw.isSubsting() and ' ' or '&nbsp;'
	
	return userLink .. space .. toolbar
end

--------------------------------------------------------------------------------
-- Single link function
--------------------------------------------------------------------------------

p.single = makeInvokeFunction('_single')

function p._single(args)
	
	local options = p.getOptions(args)
	local snippets = p.getSnippets(args)
	local links = p.getLinks(snippets)
	local code = args[1]
	local success, link = pcall(p.exportSingle, links, code)
	if success then
		return link
	else
		return makeWikitextError(link, options.isDemo)
	end
end

function p.exportSingle(links, code)
	
	if not code then
		raiseError(
			message('error-nolinkcode'),
			message('error-nolinkcode-section')
		)
	end
	return links[code]
end

--------------------------------------------------------------------------------
-- Link table
--------------------------------------------------------------------------------

function p.linktable()
	
	local args = {user = 'Example'}
	local snippets = p.getSnippets(args)
	local links = p.getLinks(snippets)

	
	local firstCodes = {'u', 't', 'c'}
	local firstLinks, firstCodesKeys = {}, {}
	for i, code in ipairs(firstCodes) do
		firstCodesKeys[code] = true
		firstLinks[#firstLinks + 1] = {code, links[code]}
	end
	local secondLinks = {}
	for code, link in pairs(links) do
		if not firstCodesKeys[code] then
			secondLinks[#secondLinks + 1] = {code, link}
		end
	end
	table.sort(secondLinks, function(t1, t2)
		return t1[1] < t2[1]
	end)
	local links = {}
	for i, t in ipairs(firstLinks) do
		links[#links + 1] = t
	end
	for i, t in ipairs(secondLinks) do
		links[#links + 1] = t
	end

	
	local ret = {}
	ret[#ret + 1] = '{| class="wikitable plainlinks sortable"'
	ret[#ret + 1] = '|-'
	ret[#ret + 1] = '! ' .. message('linktable-codeheader')
	ret[#ret + 1] = '! ' .. message('linktable-previewheader')
	for i, t in ipairs(links) do
		local code = t[1]
		local link = t[2]
		ret[#ret + 1] = '|-'
		ret[#ret + 1] = "| '''" .. code .. "'''" 
		ret[#ret + 1] = '| ' .. link
	end
	ret[#ret + 1] = '|}'
	return table.concat(ret, '\n')
end 

return p