local rp = {}
local r = {}
local hyphen2dash = require('Module:String2').hyphen_to_dash
local lang = require('Module:Lang')
local trim_quotes = require('Module:trim quotes')._trim
local getArgs = require('Module:Arguments').getArgs
local plainText = require('Module:Plain text')._main
local mt = {__call = function (t, v) t[#t+1] = v end}

local notblank = function(v) return (v or '') ~= '' end

local pageTest = {pages = 1, pp = 1, page = 1, p = 1}
local wrapTest = {yes = 1, y = 1, forced = 1, f = 1}

-- get first non-empty argument from a list of names
local function firstArg(args, ...)
	for i = 1, select('#', ...) do
		local n = select(i, ...)
		local v = args[n]
		if notblank(v) then return v end
	end
	return nil
end

local function firstArgN(s, args, ...)
	for i = 1, select('#', ...) do
		local n = select(i, ...)
		local v = args[n .. s]
		if notblank(v) then return v end
	end
	return nil
end

-- Implements [[Template:page_needed]]
-- may replace with lua implementation at some point
local function page_needed(frame, date, reason)
	return frame:expandTemplate{title='Page needed', args = {date = date, reason = reason}}
end

-- Implements [[Template:R/langcode]]
-- Only for template/module use: |language=code/name
function r._langcode(language)
	language = language or ''
	local tag, result = lang._tag_from_name{language}
	if result then
		return tag
	elseif lang._is_ietf_tag(language) then
		return language
	end
	return 'mis' -- use language code "mis" for any unrecognized language
end
function r.langcode(frame)
	local args = getArgs(frame)
	return r._langcode(args.language)
end


-- Implements [[Template:R/where]] sub-template
-- Only for template/module use: |plural=pages |singular=page |location=in-source-location |spacing=character
function r._where(params)
	local out = setmetatable({}, mt)

	if notblank(params.plural) then
		out(params.plural)
		if notblank(params.singular) then
			out("," .. (params.spacing or "") .. "[" .. params.singular .. "]")
		end
	elseif notblank(params.singular) then
		out(params.singular)
	end

	if notblank(params.location) then
		if notblank(params.plural) or notblank(params.singular) then
			out("," .. (params.spacing or ""))
		end
		out(params.location)
	end

	return table.concat(out)
end
function r.where(frame)
	return r._where(getArgs(frame))
end


-- Implements [[Template:R/superscript]]
-- Only for template/module use: |wrap=n[o]/y[es]/f[orced] |leadin=value |prefix=value |pp=value |where=value |sup-where=value |quote-where=value |quote=value |language=value |translation=value |suffix=value
function r._superscript(params)
	if not params or not notblank(params.where) then
		return ""
	end

	local out = setmetatable({}, mt)
	
	-- "wrap" if wrap is "yes", "y", "f" or "forced", else "nowrap"
	local wrap = wrapTest[string.ulower(params.wrap or '')]
	out('<sup class="reference ' .. (wrap and "" or "no") .. 'wrap">')

	--prefix, if there's a quote
	if notblank(params.quote) and notblank(params.prefix) then
		out(params.prefix)
	end
	
	-- open tooltip span
	out('<span title="')
	
	-- Lead-in text (escaped)
	local whereText = notblank(params["quote-where"]) and params["quote-where"]
		or (params["where"] or "")
	local tooltipText = plainText((params.leadin or "") .. ": " .. whereText, false)
	out(mw.text.encode(tooltipText, '<>"'))

	-- Quotation block if present
	if notblank(params.quote) then
		local quoteTab = setmetatable({}, mt)
		if notblank(params.language) then
			local langText = lang._is_ietf_tag(params.language) and lang._name_from_tag{params.language} or params.language
			quoteTab("&#32;(" .. langText .. ")")
		end
		
		quoteTab("&#58; &quot;" .. trim_quotes(params.quote) ..
			"&quot;")

		if notblank(params.translation) then
			quoteTab("&#10;Translation: &quot;" ..
				trim_quotes(params.translation) .. "&quot;")
		end
		
		local quoteText = plainText(table.concat(quoteTab), false)
		out("&#10;Quotation" .. mw.text.encode(quoteText, '<>"') ..
			'" class="tooltip tooltip-dashed" style="border-bottom: 1px dashed;')
	end
	
	-- close tooltip span
	out('">')
	
	-- prefix, if not inserted above
	if (not notblank(params.quote)) and notblank(params.prefix) then
		out(params.prefix)
	end
	
	--pp and sup-where
	out(params.pp or '')
	out(params['sup-where'] or '')
	
	-- suffix
	if notblank(params.quote) then
		out("</span>" .. (params.suffix or ""))
	else
		out((params.suffix or "") .. "</span>")
	end

	out("</sup>")

	return table.concat(out)
end
function r.superscript(frame)
	return r._superscript(getArgs(frame))
end

-- Implements [[Template:R/ref]]
function r._ref(params, frame)
	frame = frame or mw.getCurrentFrame()
	local out = setmetatable({}, mt)
	
	local contextSectionName = mw.uri.anchorEncode(
			'cite_sect-' ..
			trim_quotes(params[1] or '') ..	'-' ..
			trim_quotes(params[2] or '') .. '-' .. 
			trim_quotes(
				(notblank(params.page) and params.page or hyphen2dash(params.pages or '', ' ')) ..
				(params.location or '')
			)
		)
	local contentId = notblank(params['content-id']) and mw.uri.anchorEncode(params['content-id']) or nil
	local whereArgs = {
		plural = hyphen2dash(params.pages, ' '),
		singular = params.page,
		location = params.location,
		spacing = '&#32;'
	}
	local quoteWhereArgs = {
		plural = hyphen2dash(params['quote-pages'], ' '),
		singular = params['quote-page'],
		location = params['quote-location'],
		spacing = '&#32;'
	}
	local refArgs = {name = params[1] or '', group = params[2] or ''}
	
	-- Debug facility
	if notblank(params.debug) then
		out("DEBUG: ")
		for k, v in pairs(params) do
			out(k .. "=" .. (v or '') .. ", ")
		end
		out(". Potential context-section-name:")
		out(contextSectionName .. '".')
	end
	
	-- Opening "support context" span
	local lst
	if notblank(params.section) then
		local section = (params.section == 'yes' or params.section == 'y') and
			contextSectionName or params.section
		lst = frame:callParserFunction('#lst', {
			mw.title.getCurrentTitle().fullText,
			section
		})
		if notblank(lst) then
			out('<templatestyles src="Template:Tooltip/styles.css"/>')
			out('<span class="rt-commentedText tooltip tooltip-dotted"' ..
				'title="Context: &quot;' .. mw.text.encode(plainText(lst, false), '<>"') ..
				'&quot;">')
		end
	end
	
	--First call to create reference link (variants identical except for dir):
	if notblank(params['link-id']) then
		out('<span id="' .. mw.uri.anchorEncode(params['link-id']) ..
			'class="citation">')
	end

	local refContent = setmetatable({}, mt)
	if (not notblank(params.annotation)) then
		refContent(params.reference or '')
		if contentId then
			refContent(1, '<span id="' .. contentId .. '" class="citation">')
			refContent('</span>')
		end
		refContent(params.postscript or '')
	end

	if notblank(params.direction) then
		refArgs.dir = params.direction
	end
	out(frame:extensionTag('ref', table.concat(refContent), refArgs))
	
	if notblank(params['link-id']) then
		out('</span>')
	end
	
	--Optional second call to append data to reference (variants identical except for dir):
	if notblank(params.annotation) then
		refContent = setmetatable({}, mt)
		if pageTest[params.annotation] then
			if notblank(firstArg(params, 'pages', 'page', 'location')) then
				refContent(params.leadin or '')
				refContent(r._where(whereArgs))
				if contentId then
					refContent(1, '<span id="' .. contentId .. '">')
					refContent('</span>')
				end
				refContent(1, '&#8204;')
			end
		elseif ({quote = 1, q = 1})[params.annotation] then
			refContent('&#8204;')
			if pageTest[firstArg(params, 'quote-pages', 'quote-page', 'quote-location')] then
				if notblank(firstArg(params, 'pages', 'page', 'location')) then
					refContent(params.leadin or '')
					refContent(r._where(whereArgs) .. ':&#32;')
				end
			else
				refContent(params.leadin or '')
				refContent(r._where(quoteWhereArgs) .. ':&#32;')
			end
			
			refContent('<q ')
			if contentId then
				refContent('id="' .. contentId .. '" ')
			end
			local qcli = firstArg(params, 'quote-cite', 'link-id')
			if notblank(qcli) then
				refContent('cite="#' .. (mw.uri.anchorEncode(qcli)) .. '" ')
			end
			if notblank(params.language) then
				local langcode = r._langcode(params.language)
				refContent('lang="' .. langcode .. '">')
				refContent('<bdi lang="' .. langcode .. '">')
			else
				refContent('><bdi>')
			end
			refContent(trim_quotes(params.quote or '') .. '</bdi></q>')
			if notblank(params.translation) then
				refContent('&#32;&#91;<bdi ')
				if notblank(params.language) then
					refContent('lang="' .. r._langcode(params.language) .. '"')
				end
				refContent('>' .. trim_quotes(params.translation .. '</bdi>&#93;'))
			end
		elseif contentId then
			refContent('<span id="' .. contentId ..
				' class="citation">' .. (params.leadin or '') ..
				params.annotation .. '</span>')
		else
			refContent((params.leadin or '') .. params.annotation)
		end
		refContent(params.postscript or '')
		refArgs = {follow = params[1] or '', group = params[2] or ''}
		if notblank(params.direction) then
			refArgs.dir = params.direction
		end
		out(frame:extensionTag('ref', table.concat(refContent), refArgs))
	end
	
	--Superscript pages and tooltip for help, pages, quotes:
	local ssAma = string.ulower(params.style or '') == 'ama'
	-- p/pp is only used in superscript label, therefore it does not contain any qp params
	local ssPp = ''
	if ssAma and (not notblank(params['no-pp'])) then
		if notblank(params.pages) then
			ssPp = 'pp'
		elseif notblank(params.page) then
			ssPp = 'p'
		end
	end
	-- leadin is only used in tooltip
	local ssLeadin = 'Page&nbsp;/ location'
	if pageTest[firstArg(params, 'quote-pages', 'quote-page', 'quote-location')] then
		if notblank(params.pages) then
			ssLeadin = 'Pages'
		elseif notblank(params.page) then
			ssLeadin = 'Page'
		elseif notblank(params.location) then
			ssLeadin = 'Location'
		end
	elseif notblank(params['quote-pages']) then
		ssLeadin = 'Pages'
	elseif notblank(params['quote-page']) then
		ssLeadin = 'Page'
	elseif notblank(params['quote-location']) then
		ssLeadin = 'Location'
	end
	
	out(r._superscript{
		prefix = ssAma and '(' or '&#58;&hairsp;',
		suffix = ssAma and ')' or '&hairsp;',
		pp = ssPp,
		leadin = ssLeadin,
		where = r._where(whereArgs), -- where must not include qp params
		['sup-where'] = r._where{ -- sup-where same as where, but with improved list spacing for superscript
			plural = hyphen2dash(params.pages, '&hairsp;'),
			singular = params.page,
			location = params.location,
			spacing='&hairsp;'
		},
		['quote-where'] = r._where( -- quote-where must not contain normal in-source-location params
			pageTest[firstArg(params, 'quote-pages', 'quote-page', 'quote-location')] and
			whereArgs or quoteWhereArgs
		),
		quote = params.quote,
		language = params.language,
		translation = params.translation,
		['wrap'] = params['wrap']
	})
	
	-- Closing "support context" span:
	if notblank(params.section) and notblank(lst) then
		out('</span>')
	end
	
	-- Page needed functionality:
	if notblank(params['needed-reason']) then
		local pnReason = ({yes = 1, y = 1})[params['needed-reason']] and
			'No reason given' or params['needed-reason']
		out(page_needed(frame, params['needed-date'], pnReason))
	end
	
	-- Line wrapping functionality:
	if wrapTest[string.ulower(params.wrap or '')] then
		out("&#8203;") --zero-width space
	end
	
	--End of code
	return table.concat(out)
end
function r.ref(frame)
	return r._ref(getArgs(frame), frame)
end

function r._r(args, frame)
	frame = frame or mw.getCurrentFrame()
	local output = setmetatable({}, mt)
	-- ### 1 ###
	output(r._ref({
		[1]				= firstArg(args, 'name1', 'name', 'n1', 'n', 1),
		[2]				= firstArg(args, 'group', 'grp', 'g'),
		direction		= firstArg(args, 'direction1', 'direction', 'dir1', 'dir'),
		page			= firstArg(args, 'page1', 'page', 'p1', '1p', 'p'),
		pages			= firstArg(args, 'pages1', 'pages', 'pp1', '1pp', 'pp'),
		location		= firstArg(args, 'location1', 'location', 'loc1', '1loc', 'loc', 'at1', 'at'),
		['quote-page']	= firstArg(args, 'quotation-page1', 'quotation-page', 'quote-page1', 'quote-page', 'qp1', 'qp'),
		['quote-pages']	= firstArg(args, 'quotation-pages1', 'quotation-pages', 'quote-pages1', 'quote-pages', 'qpp1', 'qpp'),
		['quote-location'] = firstArg(args,
			'quotation-location1', 'quotation-location', 'quote-location1', 'quote-location', 'quote-loc1', 'quote-loc',
			'quote-at1', 'quote-at'
		),
		quote			= firstArg(args, 'quotation1', 'quotation', 'quote1', 'quote', 'q1', 'q'),
		language		= firstArg(args, 
			'quotation-language1', 'quotation-language', 'quote-language1', 'quote-language', 'quotation-lang1',
			'quotation-lang', 'quote-lang1', 'quote-lang', 'ql1', 'ql', 'language1', 'language', 'lang1', 'language', 'l1', 'l'
		),
		translation		= firstArg(args,
			'translation-quotation1', 'translation-quotation', 'trans-quotation1', 'trans-quotation', 'translation-quote1',
			'translation-quote', 'trans-quote1', 'trans-quote', 'tq1', 'tq', 'translation1', 'translation', 'trans1', 'trans',
			't1', 't', 'xlat1', 'xlat'
		),
		['quote-cite']	= firstArg(args, 'quotation-cite1', 'quotation-cite', 'quote-cite1', 'quote-cite', 'qc1', 'qc'),
		reference		= firstArg(args,
			'reference1', 'references', 'reference', 'notes', 'note', 'content', 'text', 'refn1', 'refn', 'refs', 'r1', 'r'
		),
		annotation		= firstArg(args, 'annotation1', 'annotation', 'annot1', 'annot', 'a1', 'a'),
		leadin			= args.leadin,
		postscript		= firstArg(args, 'postscript', 'postscript1', 'ps', 'ps1', '1ps'),
		section			= firstArg(args, 'section1', 'section', 'sec1', 'sec', 's1', 's'),
		['needed-reason'] = firstArg(args, 'needed-reason1', 'needed-reason', 'needed1', 'needed', 'reason'),
		['needed-date']	= firstArg(args, 'needed-date', 'date'),
		wrap			= args.wrap,
		['no-pp']		= firstArg(args, 'no-pp', 'nopp'),
		style			= args.style,
		['content-id']	= firstArg(args, 'ref1', '1ref', 'ref', 'id1', 'id'),
		['link-id']		= firstArg(args, 'link-id1', 'link-id'),
		['debug']		= args['debug']
	}, frame))

	-- ### 2+ ###
	local n = 2
	while firstArg(args, "name" .. n, "n" .. n, n) ~= nil do
		output(r._ref({
			[1]				= firstArg(args, 'name' .. n, 'n' .. n, n),
			[2]				= firstArg(args, 'group', 'grp', 'g'),
			direction		= firstArgN(n, args, 'direction', 'dir'),
			page			= firstArg(args, 'page' .. n, 'p' .. n, n .. 'p'),
			pages			= firstArg(args, 'pages' .. n, 'pp' .. n, n .. 'pp'),
			location		= firstArg(args, 'location' .. n, 'loc' .. n, n .. 'loc', 'at' .. n),
			['quote-page']	= firstArgN(n, args, 'quotation-page', 'quote-page', 'qp'),
			['quote-pages']	= firstArgN(n, args, 'quotation-pages', 'quote-pages', 'qpp'),
			['quote-location'] = firstArgN(n, args, 'quotation-location', 'quote-location', 'quote-loc', 'quote-at'),
			quote			= firstArgN(n, args, 'quotation', 'quote', 'q'),
			language		= firstArgN(n, args,
				'quotation-language', 'quote-language',	'quotation-lang', 'quote-lang', 'ql', 'language', 'lang', 'l'
			),
			translation		= firstArgN(n, args,
				'translation-quotation', 'trans-quotation', 'translation-quote', 'trans-quote', 'tq', 'translation', 'trans', 't',
				'xlat'
			),
			['quote-cite']	= firstArgN(n, args, 'quotation-cite', 'quote-cite', 'qc'),
			reference		= firstArgN(n, args, 'reference', 'refn', 'r'),
			annotation		= firstArgN(n, args, 'annotation', 'annot', 'a'),
			leadin			= args.leadin,
			postscript		= firstArg(args, 'postscript' .. n, 'ps' .. n, n .. 'ps', 'postscript', 'ps'),
			section			= firstArgN(n, args, 'section', 'sec', 's'),
			['needed-reason'] = firstArgN(n, args, 'needed-reason', 'needed'),
			['needed-date']	= firstArg(args, 'needed-date', 'date'),
			['wrap']		= args['wrap'],
			['no-pp']		= firstArg(args, 'no-pp', 'nopp'),
			style			= args.style,
			['content-id']	= firstArg(args, 'ref' .. n, n .. 'ref', 'id' .. n),
			['link-id']		= args['link-id' .. n],
			['debug']		= args['debug'],
		}, frame))
		n = n + 1
	end
	return table.concat(output)
end
function r.r(frame)
	return r._r(getArgs(frame), frame)
end

-- Implements [[Template:Rp]]
function rp._rp(args, frame)
	frame = frame or mw.getCurrentFrame()
	-- If needed param is present and non-empty, show {{page needed}}
	if notblank(args.needed) then
		return page_needed(frame, args.date, args.reason)
	end

	-- prefix/suffix depending on style
	local ama = string.ulower(args.style or "") == "ama"
	local prefix = ama and "(" or "&#58;&hairsp;"
	local suffix = ama and ")" or "&hairsp;"

	-- p/pp is only used in superscript label, therefore it does not contain any qp params
	local pp = ""
	if ama and not notblank(args["no-pp"]) and not notblank(args.nopp) then
		if notblank(args.pages) or notblank(args.pp) then
			pp = "pp"
		elseif notblank(args.page) or notblank(args.p) then
			pp = "p"
		end
	end
	
	-- leadin is only used in tooltip
	local leadin
	local quote_switch = firstArg(args, "quotation-pages", "quote-pages", "qpp",
		"quotation-page", "quote-page", "qp",
		"quotation-location", "quote-location", "quote-loc", "quote-at")

	if not pageTest[quote_switch] then
		if notblank(firstArg(args, "quotation-pages", "quote-pages", "qpp")) then
			leadin = "Pages"
		elseif notblank(firstArg(args, "quotation-page", "quote-page", "qp")) then
			leadin = "Page"
		elseif notblank(firstArg(args, "quotation-location", "quote-location", "quote-loc", "quote-at")) then
			leadin = "Location"
		end
	end
	if not leadin then
		--either the quote_switch wasn't 'pages', 'pp', 'page',
		-- or 'p', or the 'quotation' params were all blank/nil
		if notblank(firstArg(args, "pages", "pp")) then
			leadin = "Pages"
		elseif notblank(firstArg(args, "page", "p")) then
			leadin = "Page"
		elseif notblank(firstArg(args, "location", "loc", "at")) then
			leadin = "Location"
		else
			leadin = "Page&nbsp;/ location"
		end
	end

	-- where must not include qp params
	local where = r._where{
		plural = hyphen2dash(firstArg(args, "pages", "pp", "1"), '&#32;'),
		singular = firstArg(args, "page", "p"),
		location = firstArg(args, "location", "loc", "at"),
		spacing = "&#32;"
	}

	-- sup-where same as where, but with improved list spacing for superscript
	local sup_where = r._where{
		plural = hyphen2dash(firstArg(args, "pages", "pp", "1"), '&hairsp;'),
		singular = firstArg(args, "page", "p"),
		location = firstArg(args, "location", "loc", "at"),
		spacing = "&hairsp;"
	}

	-- quote-where must not contain normal in-source-location params
	local quote_where
	if pageTest[quote_switch] then
		quote_where = r._where{
			plural = hyphen2dash(firstArg(args, "pages", "pp", 1), ' '),
			singular = firstArg(args, "page", "p"),
			location = firstArg(args, "location", "loc", "at"),
			spacing = "&#32;"
		}
	else
		quote_where = r._where{
			plural = hyphen2dash(firstArg(args, "quotation-pages", "quote-pages", "qpp"), ' '),
			singular = firstArg(args, "quotation-page", "quote-page", "qp"),
			location = firstArg(args, "quotation-location", "quote-location", "quote-loc", "quote-at"),
			spacing = "&#32;"
		}
	end

	local zws = ""
	if wrapTest[string.ulower(args.wrap or '')] then
		zws = "&#8203;" --zero-width space
	end

	return r._superscript{
		prefix = prefix,
		suffix = suffix,
		pp = pp,
		leadin = leadin,
		["where"] = where,
		["sup-where"] = sup_where,
		["quote-where"] = quote_where,
		quote = firstArg(args, "quotation", "quote", "q"),
		language = firstArg(args, "quotation-language", "quote-language",
			"quotation-lang", "quote-lang", "ql", "language", "lang", "l"),
		translation = firstArg(args, "translation-quotation", "trans-quotation",
			"translation-quote", "trans-quote", "tq", "translation", "trans",
			"t", "xlat"),
		["wrap"] = args.wrap
	} .. zws
end
function rp.rp(frame)
	return rp._rp(getArgs(frame), frame)
end

return {
	[''] = r.r,
	r = r.r,
	_r = r._r,
	rp = rp.rp,
	_rp = rp._rp,
	ref = r.ref,
	_ref = r._ref,
	where = r.where,
	_where = r._where,
	langcode = r.langcode,
	_langcode = r._langcode,
	superscript = r.superscript,
	_superscript = r._superscript
}