This module is used for the various Finnish nominal inflection tables.


local export = {}

local lang = require("Module:languages").getByCode("fi")

-- Functions that do the actual inflecting by creating the forms of a basic term.
local inflections = {}

local kotus_grad_type = {
	["kk-k"] = "A",
	["pp-p"] = "B",
	["tt-t"] = "C",
	["k-"] = "D",
	["p-v"] = "E",
	["t-d"] = "F",
	["nk-ng"] = "G",
	["mp-mm"] = "H",
	["lt-ll"] = "I",
	["nt-nn"] = "J",
	["rt-rr"] = "K",
	["k-j"] = "L",
	["k-v"] = "M"
}

local m_bit32 -- loaded later if needed

local RARE = '<span class="narrow-space"> </span><sup>rare</sup>'

local function normalize_apostrophes(term, link_target)
	if link_target then
		if term and mw.ustring.find(term, "’") then
			term = mw.ustring.gsub(term, "’", "'")
		end
	else
		if term and mw.ustring.find(term, "'") then
			term = mw.ustring.gsub(term, "'", "’")
		end
	end
	return term
end


-- Creates a link to a form.
local function make_link(term, accel_form)
	-- do not link inflected forms of suffixes
	if term:match("^-") then
		if term == mw.title.getCurrentTitle().fullText then
			return '<span class="Latn" lang="fi"><strong class="selflink">' .. term .. '</strong></span>'
		end

		return '<span class="Latn" lang="fi">' .. term .. '</span>'
	end
	
	-- if there is something difficult, use full module.
	if term:find(":") or term:find("<") then
		if term:find(":") then term = mw.ustring.gsub(term, ":", "\\:") end
		
		return require("Module:links").full_link({
			lang = lang,
			term = term,
			accel = accel_form and ({ form = accel_form }) or nil
		})
	end
	
	-- otherwise, we can save a ton of memory by doing this manually.
	local target = normalize_apostrophes(term, true)
	
	if target == mw.title.getCurrentTitle().fullText then
		return '<span class="Latn" lang="fi"><strong class="selflink">' .. term .. '</strong></span>'
	end
	
	if not accel_form then
		return '<span class="Latn" lang="fi">[[' .. target .. '#Finnish|' .. term .. ']]</span>'
	end
	
	return '<span class="Latn form-of lang-fi ' .. accel_form .. '-form-of" lang="fi">[[' .. target .. '#Finnish|' .. term .. ']]</span>'
end

local function tag_term(term)
	-- return require("Module:script utilities").tag_text(term, lang, nil, "term")
	return '<i class="Latn mention" lang="fi">' .. term .. '</i>'
end

local function do_inflection_internal(data, argobj)
	local args = argobj.args
	argobj.pos = 1

	data.words = {}
	data.num = 1
	data.forms = nil
	data.categories = {}
	
	for num, infl_type in ipairs(data.infl_types) do
		-- initialize data for single word
		local word_data = {forms = {}, title = nil, categories = {}}
		
		-- word index
		word_data.num = num
		data.num = num
		
		-- Generate the forms
		inflections[infl_type](argobj, word_data)
		postprocess_word(argobj, word_data, data, num == #data.infl_types)
		word_data.class = infl_type
		data.words[num] = word_data
	end

	if #data.words > 1 then
		-- join the inflected word components
		export.join_words(data, function (n) return args["space" .. tostring(n)] or args["space"] or " " end)
	else
		data.vh = data.words[1].vh
		data.forms = data.words[1].forms
		data.title = data.words[1].title
		data.categories = data.words[1].categories
	end
	
	-- Postprocess
	postprocess(args, data)
end

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
	local infl_type = frame.args[1] or error("Inflection type has not been specified. Please pass parameter 1 to the module invocation")
	local args = frame:getParent().args
	local infl_types = {infl_type}
	
	infl_types = mw.text.split(infl_type, "%-")
	
	for _, type in ipairs(infl_types) do
		if not inflections[type] then
			error("Unknown inflection type '" .. infl_type .. "'")
		end
	end
	
	local pos = args["pos"]; if not pos or pos == "" then pos = "noun" end
	local allow_possessive = (pos == "noun" or pos == "adj") and not args["poss"]
	
	-- initialize data for full inflection process
	local data = {
		pagename = mw.title.getCurrentTitle().text,
		infl_types = infl_types,
		poss = args["poss"]
	}
	local argobj = {args = args}

	if args["title"] and mw.title.getCurrentTitle().namespace > 0 then
		data.pagename = args.title
	elseif data.pagename:find("^Unsupported titles/") then
		data.pagename = data.pagename:gsub("^Unsupported titles/", "")
	end
	
	data.pagename = normalize_apostrophes(data.pagename)

	do_inflection_internal(data, argobj)

	--if args["type"] then table.insert(data.categories, "fi-decl with type") end
	--if args["nocheck"] then table.insert(data.categories, "fi-decl with nocheck") end
	--if args["nosg"] then table.insert(data.categories, "fi-decl with nosg") end
	--if args["nopl"] then table.insert(data.categories, "fi-decl with nopl") end
	
	local function get_poss_forms(poss)
		data.poss = poss
		do_inflection_internal(data, argobj)
		return data
	end

	local categories
	if args["appendix"] then
		categories = ""
	else
		categories = require("Module:utilities").format_categories(data.categories, lang)
	end

	return make_table(data, true)
	    .. (allow_possessive and make_possessive_table(data.pagename, args, pos, data.title, get_poss_forms) or "")
	    .. categories
		.. require("Module:TemplateStyles")("Module:fi-nominals/style.css")
end

local function args_get_required(args, i, purpose)
	local v = args[i]
	if not v then
		error(purpose .. " (parameter " .. i .. ") may not be omitted.")
	end
	return v
end

local function args_get_vowel_harmony(args, i)
	local v = args[i]
	if not v or not mw.ustring.match(v, "^[aä]$") then
		error("Vowel harmony (parameter " .. i .. ") must be \"a\" or \"ä\".")
	end
	return v
end

function get_params(argobj, num, invert_grades)
	local params = {}
	local args = argobj.args
	local pos = argobj.pos

	params.base = normalize_apostrophes(args[pos])
	if num >= 2 then
		if num >= 4 then
			params.strong = normalize_apostrophes(args_get_required(args, pos + 1, "Nominative grade"))
			params.weak = normalize_apostrophes(args_get_required(args, pos + 2, "Genitive grade"))
	
			-- Swap the grades
			if invert_grades then
				params.strong, params.weak = params.weak, params.strong
			end
		end

		if num >= 5 then
			params.final = args_get_required(args, pos + 3, "Final letter(s)")
		end

		params.a = args_get_vowel_harmony(args, pos + num - 1)
	else
		params.base = params.base or ""
	end
	
	if params.a then
		params.o = params.a == "ä" and "ö" or "o"
		params.u = params.a == "ä" and "y" or "u"
	end
	
	argobj.pos = argobj.pos + num
	return params	
end

function get_extra_arg(argobj, wdata, name, fallback)
	return argobj.args[name .. wdata.num] or argobj.args[name]
end

--[=[
	Inflection functions
]=]--

local stem_endings = {}

stem_endings["nom_sg"] = {
	["nom_sg"] = "",
}

stem_endings["sg"] = {
	["ess_sg"] = "na",
}

stem_endings["sg_weak"] = {
	["gen_sg"] = "n",
	["ine_sg"] = "ssa",
	["ela_sg"] = "sta",
	["ade_sg"] = "lla",
	["abl_sg"] = "lta",
	["all_sg"] = "lle",
	["tra_sg"] = "ksi",
	["ins_sg"] = "n",
	["abe_sg"] = "tta",
	["nom_pl"] = "t",
}

stem_endings["par_sg"] = {
	["par_sg"] = "a",
}

stem_endings["ill_sg"] = {
	["ill_sg"] = "Vn",
}

stem_endings["pl"] = {
	["ess_pl"] = "na",
	["com_pl"] = "ne",
}

stem_endings["pl_weak"] = {
	["ine_pl"] = "ssa",
	["ela_pl"] = "sta",
	["ade_pl"] = "lla",
	["abl_pl"] = "lta",
	["all_pl"] = "lle",
	["tra_pl"] = "ksi",
	["ins_pl"] = "n",
	["abe_pl"] = "tta",
}

stem_endings["par_pl"] = {
	["par_pl"] = "a",
}

stem_endings["gen_pl"] = {
	["gen_pl"] = "en",
}

stem_endings["ill_pl"] = {
	["ill_pl"] = "Vn",
}

-- Make a copy of the endings, with front vowels
stem_endings = {["a"] = stem_endings, ["ä"] = mw.clone(stem_endings)}

for stem_key, endings in pairs(stem_endings["ä"]) do
	for key, ending in pairs(endings) do
		endings[key] = mw.ustring.gsub(endings[key], "([aou])", {["a"] = "ä", ["o"] = "ö", ["u"] = "y"})
	end
end

-- data for generating possessive forms
-- suffixes per person
local poss_forms = {["1s"] = "ni", ["2s"] = "si", ["3s"] = "nsa", ["1p"] = "mme", ["2p"] = "nne"}
local poss_alt = {--["00"] = false, ["1s"] = false, ["2s"] = false, ["1p"] = false, ["2p"] = false,
					["3s"] = true, -- shorter form -Vn 
					}
-- which forms allow -nsa > -Vn?
local forms_alt_ok = {
	["gen_sg"] = false, ["gen_pl"] = false,
	["par_sg"] = false, ["par_pl"] = true,
	["ine_sg"] = true, ["ine_pl"] = true,
	["ela_sg"] = true, ["ela_pl"] = true,
	["ill_sg"] = false, ["ill_pl"] = false,
	["ade_sg"] = true, ["ade_pl"] = true,
	["abl_sg"] = true, ["abl_pl"] = true,
	["all_sg"] = true, ["all_pl"] = true,
	["ess_sg"] = true, ["ess_pl"] = true,
	["tra_sg"] = true, ["tra_pl"] = true,
	["ins_sg"] = false, ["ins_pl"] = false,
	["abe_sg"] = true, ["abe_pl"] = true,
	["com_sg"] = true, ["com_pl"] = true,
}
-- which forms end in -n?
-- (in which case it is dropped before the possessive suffix)
local forms_gen_ok = {
	["gen_sg"] = true, ["gen_pl"] = true,
	["ill_sg"] = true, ["ill_pl"] = true,
	["ins_sg"] = true, ["ins_pl"] = true,
}

local function feed_list(outputs, inputs)
	for key, values in pairs(inputs) do
		outputs[key] = outputs[key] or {}
		for _, value in ipairs(values) do
			table.insert(outputs[key], value)
		end
	end
end

local function process_stems(data, stems, vh)
	-- Create any stems that were not given
	stems["nom_sg"] = stems["nom_sg"] or mw.clone(stems["sg"])
	stems["par_sg"] = stems["par_sg"] or mw.clone(stems["sg"])
	stems["par_pl"] = stems["par_pl"] or mw.clone(stems["pl"])
	stems["ill_pl"] = stems["ill_pl"] or mw.clone(stems["pl"])
	
	if not stems["ill_sg"] and stems["sg"] then
		stems["ill_sg"] = {}
		
		for _, stem in ipairs(stems["sg"]) do
			-- If the stem ends in a long vowel or diphthong, then add -h
			if mw.ustring.find(stem, "([aeiouyäö])%1$") or mw.ustring.find(stem, "[aeiouyäö][iuyü]$") then
				table.insert(stems["ill_sg"], stem .. "h")
			else
				table.insert(stems["ill_sg"], stem)
			end
		end
	end
	
	if not stems["gen_pl"] and stems["par_pl"] then
		stems["gen_pl"] = {}
		
		for _, stem in ipairs(stems["par_pl"]) do
			-- If the partitive plural stem ends in -it, then replace the t with d or tt
			if mw.ustring.find(stem, "it$") then
				table.insert(stems["gen_pl"], (mw.ustring.gsub(stem, "t$", "d")))
				table.insert(stems["gen_pl"], stem .. "t")
			else
				table.insert(stems["gen_pl"], stem)
			end
		end
	end

	-- Create forms based on each stem, by adding endings to it
	stems["sg_weak"] = stems["sg_weak"] or mw.clone(stems["sg"])
	stems["pl_weak"] = stems["pl_weak"] or mw.clone(stems["pl"])
	
	-- Go through each of the stems given
	for stem_key, substems in pairs(stems) do
		for _, stem in ipairs(substems) do
			-- Attach the endings to the stem
			for form_key, ending in pairs(stem_endings[vh][stem_key]) do
				if not data.forms[form_key] then
					data.forms[form_key] = {}
				end
				
				-- "V" is a copy of the last vowel in the stem
				if mw.ustring.find(ending, "V") then
					local vowel = mw.ustring.match(stem, "([aeiouyäö])[^aeiouyäö]*$")
					ending = mw.ustring.gsub(ending, "V", vowel or "?")
				end
				
				table.insert(data.forms[form_key], stem .. ending)
			end
		end
	end
	
	data["stems"] = stems
	data["vh"] = vh
end


local function make_kotus_title_number(type_number)
	return "[[Kotus]] type " .. type_number
end


local function make_kotus_title_word(reference_word)
	return '/<span lang="fi" class="Latn">[[Appendix:Finnish declension/' .. reference_word .. '|' .. reference_word .. ']]</span>'
end


local function make_kotus_title(number, reference_word)
	return make_kotus_title_number(number) .. make_kotus_title_word(reference_word)
end


local function inflection_type_is(data, number, reference_word, strong, weak)
	local title = make_kotus_title_number(number)
	local has_gradation = strong and strong ~= weak

	if has_gradation then
		local letter = kotus_grad_type[strong .. "-" .. weak]
		if letter then
			title = title .. "*" .. letter
		else
			title = title .. "*"
		end
	end

	title = title .. make_kotus_title_word(reference_word)

	if has_gradation then
		local EMPTY = "<small>∅</small>"
		local function format(grade)
			if grade == "" then
				return EMPTY
			else
				return "''" .. grade .. "''"
			end
		end
		title = title .. ", " .. format(strong) .. "-" .. format(weak) .. " gradation"
	else
		title = title .. ", no gradation"
	end

	data.title = title

	table.insert(data.categories, "Finnish " .. reference_word .. "-type nominals")
end


--[=[
	Inflection types
]=]--


inflections["valo"] = function(args, data)
	local params = get_params(args, 5)
	
	local wk_sg = params.weak
	local wk_pl = params.weak
	
	if mw.ustring.sub(params.base, -1) == params.final then
		if wk_sg == "" and (mw.ustring.find(params.base, "[aeiouyäö][iuy]$") or mw.ustring.find(params.base, "[iuy][eoö]$")) then
			wk_sg = "’"
		end
		
		if wk_pl == "" then
			wk_pl = "’"
		end
	end
	
	local stems = {}
	stems["sg"]      = {params.base .. params.strong .. params.final}
	stems["sg_weak"] = {params.base .. wk_sg .. params.final}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "i"}
	stems["pl_weak"] = {params.base .. wk_pl .. params.final .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. params.final .. "j"}
	stems["ill_pl"]  = {params.base .. params.strong .. params.final .. "ih"}
	
	inflection_type_is(data, 1, "valo", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["palvelu"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["sg"]      = {params.base}
	stems["pl"]      = {params.base .. "i"}
	stems["par_pl"]  = {params.base .. "j", params.base .. "it"}
	stems["ill_pl"]  = {params.base .. "ih"}
	
	inflection_type_is(data, 2, "palvelu")
	process_stems(data, stems, params.a)
end

inflections["valtio"] = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	
	local stems = {}
	stems["sg"]      = {params.base}
	stems["par_sg"]  = {params.base .. "t"}
	stems["pl"]      = {params.base .. "i"}
	stems["par_pl"]  = {params.base .. "it"}
	stems["ill_pl"]  = {params.base .. "ih"}
	
	inflection_type_is(data, 3, "valtio")
	process_stems(data, stems, params.a)
end

inflections["laatikko"] = function(args, data)
	local params = get_params(args, 5, false, "kk", "k", "o")
	
	local stems = {}
	stems["sg"]      = {params.base .. params.strong .. params.final}
	stems["sg_weak"] = {params.base .. params.weak .. params.final}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "i"}
	stems["pl_weak"] = {params.base .. params.weak .. params.final .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. params.final .. "j", params.base .. params.weak .. params.final .. "it"}
	stems["ill_pl"]  = {params.base .. params.strong .. params.final .. "ih", params.base .. params.weak .. params.final .. "ih"}
	
	inflection_type_is(data, 4, "laatikko", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["risti"] = function(args, data)
	local params = get_params(args, 4)
	local i = get_extra_arg(args, data, "i"); if i == "0" then i = "" else i = "i" end

	local stems = {}
	stems["nom_sg"]  = {params.base .. params.strong .. i}
	stems["sg"]      = {params.base .. params.strong .. "i"}
	stems["sg_weak"] = {params.base .. params.weak .. "i"}
	stems["pl"]      = {params.base .. params.strong .. "ei"}
	stems["pl_weak"] = {params.base .. params.weak .. "ei"}
	stems["gen_pl"]  = {params.base .. params.strong .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. "ej"}
	stems["ill_pl"]  = {params.base .. params.strong .. "eih"}
	
	inflection_type_is(data, 5, "risti", params.strong, params.weak)	
	process_stems(data, stems, params.a)
end

inflections["paperi"] = function(args, data)
	local params = get_params(args, 2)
	local i = get_extra_arg(args, data, "i"); if i == "0" then i = "" else i = "i" end
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. i}
	stems["sg"]      = {params.base .. "i"}
	stems["pl"]      = {params.base .. "ei"}
	stems["par_pl"]  = {params.base .. "eit", params.base .. "ej"}
	stems["gen_pl"]  = {params.base .. "i", params.base .. "eid", params.base .. "eitt"}
	stems["ill_pl"]  = {params.base .. "eih"}
	
	inflection_type_is(data, 6, "paperi")
	process_stems(data, stems, params.a)
end

inflections["ovi"] = function(args, data)
	local params = get_params(args, 4)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local wk_pl = params.weak
	if mw.ustring.sub(params.base, -1) == "i" and params.strong == "k" and params.weak == "" then
		wk_pl = "’"
	end

	local stems = {}
	stems["nom_sg"]  = {nom_sg or params.base .. params.strong .. "i"}
	stems["sg"]      = {params.base .. params.strong .. "e"}
	stems["sg_weak"] = {params.base .. params.weak .. "e"}
	stems["pl"]      = {params.base .. params.strong .. "i"}
	stems["pl_weak"] = {params.base .. wk_pl .. "i"}
	
	inflection_type_is(data, 7, "ovi", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["nalle"] = function(args, data)
	local params = get_params(args, 4)
	
	local stems = {}
	stems["sg"]      = {params.base .. params.strong .. "e"}
	stems["sg_weak"] = {params.base .. params.weak .. "e"}
	stems["pl"]      = {params.base .. params.strong .. "ei"}
	stems["pl_weak"] = {params.base .. params.weak .. "ei"}
	stems["par_pl"]  = {params.base .. params.strong .. "ej"}
	stems["ill_pl"]  = {params.base .. params.strong .. "eih"}
	
	inflection_type_is(data, 8, "nalle", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.strong .. "ein"}
end

inflections["kala"] = function(args, data)
	local params = get_params(args, 4)
	local ain = get_extra_arg(args, data, "ain"); if ain == "" then ain = nil end
	
	local wk_sg = params.weak
	
	if wk_sg == "" and mw.ustring.sub(params.base, -2) == params.a .. params.a then
		wk_sg = "’"
	end
	
	local stems = {}
	stems["sg"]      = {params.base .. params.strong .. params.a}
	stems["sg_weak"] = {params.base .. wk_sg .. params.a}
	stems["pl"]      = {params.base .. params.strong .. params.o .. "i"}
	stems["pl_weak"] = {params.base .. params.weak .. params.o .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. params.o .. "j"}
	stems["ill_pl"]  = {params.base .. params.strong .. params.o .. "ih"}
	
	inflection_type_is(data, 9, "kala", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	local ain_form = params.base .. params.strong .. params.a .. "in"
	if ain == "2" then
		table.insert(data.forms["gen_pl"], 1, ain_form)
	elseif ain == "1" then
		table.insert(data.forms["gen_pl"], ain_form)
	else
		data.forms["gen_pl"].rare = {ain_form}
	end
end

inflections["koira"] = function(args, data)
	local params = get_params(args, 4)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	local apo_pl = get_extra_arg(args, data, "apo_pl"); if apo_pl == "" then apo_pl = nil end
	local ain = get_extra_arg(args, data, "ain"); if ain == "" then ain = nil end
	
	local wk_sg = params.weak
	local wk_pl = params.weak
	
	if wk_sg == "" and mw.ustring.sub(params.base, -2) == params.a .. params.a then
		wk_sg = "’"
	end
	
	if wk_pl == "" and mw.ustring.sub(params.base, -1) == "i" then
		wk_pl = "’"
	end
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or params.base .. params.strong .. params.a}
	stems["sg"]      = {params.base .. params.strong .. params.a}
	stems["sg_weak"] = {params.base .. wk_sg .. params.a}
	stems["pl"]      = {params.base .. params.strong .. (apo_pl and "'" or "") .. "i"}
	stems["pl_weak"] = {params.base .. wk_pl .. (apo_pl and "'" or "") .. "i"}
	
	inflection_type_is(data, 10, "koira", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	local ain_form = params.base .. params.strong .. params.a .. "in"
	if ain == "2" then
		table.insert(data.forms["gen_pl"], 1, ain_form)
	elseif ain == "1" then
		table.insert(data.forms["gen_pl"], ain_form)
	else
		data.forms["gen_pl"].rare = {ain_form}
	end
end

inflections["omena"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["sg"]      = {params.base .. params.a}
	stems["pl"]      = {params.base .. params.o .. "i", params.base .. "i"}
	stems["par_pl"]  = {params.base .. "i", params.base .. params.o .. "it"}
	stems["ill_pl"]  = {params.base .. "i", params.base .. params.o .. "ih"}
	
	inflection_type_is(data, 11, "omena")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.o .. "jen", params.base .. params.a .. "in"}
	data.forms["par_pl"].rare = {params.base .. params.o .. "j" .. params.a}
end

inflections["kulkija"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["sg"]      = {params.base .. params.a}
	stems["pl"]      = {params.base .. params.o .. "i"}
	stems["par_pl"]  = {params.base .. params.o .. "it"}
	stems["ill_pl"]  = {params.base .. params.o .. "ih"}
	
	inflection_type_is(data, 12, "kulkija")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.a .. "in"}
end

inflections["katiska"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["sg"]      = {params.base .. params.a}
	stems["pl"]      = {params.base .. params.o .. "i"}
	stems["par_pl"]  = {params.base .. params.o .. "it", params.base .. params.o .. "j"}
	stems["ill_pl"]  = {params.base .. params.o .. "ih"}
	
	inflection_type_is(data, 13, "katiska")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.a .. "in"}
end

inflections["solakka"] = function(args, data)
	local params = get_params(args, 4)
	
	local stems = {}
	stems["sg"]      = {params.base .. params.strong .. params.a}
	stems["sg_weak"] = {params.base .. params.weak .. params.a}
	stems["pl"]      = {params.base .. params.strong .. params.o .. "i"}
	stems["pl_weak"] = {params.base .. params.weak .. params.o .. "i"}
	stems["par_pl"]  = {params.base .. params.weak .. params.o .. "it", params.base .. params.strong .. params.o .. "j"}
	stems["ill_pl"]  = {params.base .. params.weak .. params.o .. "ih", params.base .. params.strong .. params.o .. "ih"}
	
	inflection_type_is(data, 14, "solakka", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.strong .. params.a .. "in"}
end

inflections["korkea"] = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	
	local stems = {}
	stems["sg"]      = {params.base .. params.a}
	stems["par_sg"]  = {params.base .. params.a, params.base .. params.a .. "t"}
	stems["pl"]      = {params.base .. "i"}
	stems["par_pl"]  = {params.base .. "it"}
	stems["ill_pl"]  = {params.base .. "isi", params.base .. "ih"}
	
	inflection_type_is(data, 15, "korkea")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.a .. "in"}
end

inflections["vanhempi"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "mpi"}
	stems["sg"]      = {params.base .. "mp" .. params.a}
	stems["sg_weak"] = {params.base .. "mm" .. params.a}
	stems["pl"]      = {params.base .. "mpi"}
	stems["pl_weak"] = {params.base .. "mmi"}
	
	inflection_type_is(data, 16, "vanhempi", "mp", "mm")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "mp" .. params.a .. "in"}
end

inflections["vapaa"] = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	
	local stems = {}
	stems["sg"]      = {params.base .. final}
	stems["par_sg"]  = {params.base .. final .. "t"}
	stems["ill_sg"]  = {params.base .. final .. "se"}
	stems["pl"]      = {params.base .. "i"}
	stems["par_pl"]  = {params.base .. "it"}
	stems["ill_pl"]  = {params.base .. "isi"}
	
	inflection_type_is(data, 17, "vapaa")
	process_stems(data, stems, params.a)
	
	data.forms["ill_pl"].rare = {params.base .. "ihin"}
end

inflections["maa"] = function(args, data)
	local params = get_params(args, 2)
	
	local pl_stem = mw.ustring.sub(params.base, 1, -2)
	
	local stems = {}
	stems["sg"]      = {params.base}
	stems["par_sg"]  = {params.base .. "t"}
	stems["pl"]      = {pl_stem .. "i"}
	stems["par_pl"]  = {pl_stem .. "it"}
	stems["ill_pl"]  = {pl_stem .. "ih"}
	
	inflection_type_is(data, 18, "maa")
	process_stems(data, stems, params.a)
end

inflections["suo"] = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	local stem = mw.ustring.sub(params.base, 1, -3)
	local plural
	if mw.ustring.sub(stem, -1) == final then
		plural = stem .. "-" .. final
	elseif mw.ustring.find(stem, "-$") and mw.ustring.sub(stem, -1) ~= final then
		plural = mw.ustring.sub(stem, 1, -2) .. final
	else
		plural = stem .. final
	end
	
	local stems = {}
	stems["sg"]      = {params.base}
	stems["par_sg"]  = {params.base .. "t"}
	stems["ill_sg"]  = {params.base .. "h"}
	stems["pl"]      = {plural .. "i"}
	stems["par_pl"]  = {plural .. "it"}
	stems["ill_pl"]  = {plural .. "ih"}
	
	inflection_type_is(data, 19, "suo")
	process_stems(data, stems, params.a)
end

inflections["filee"] = function(args, data)
	local params = get_params(args, 2)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	local pl_stem = mw.ustring.sub(params.base, 1, -2)
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or params.base}
	stems["sg"]      = {params.base}
	stems["par_sg"]  = {params.base .. "t"}
	stems["ill_sg"]  = {params.base .. "h", params.base .. "se"}
	stems["pl"]      = {pl_stem .. "i"}
	stems["par_pl"]  = {pl_stem .. "it"}
	stems["ill_pl"]  = {pl_stem .. "ih", pl_stem .. "isi"}
	
	inflection_type_is(data, 20, "filee")
	process_stems(data, stems, params.a)
end

inflections["rosé"] = function(args, data)
	local params = get_params(args, 2)
	local ill_sg_vowel = get_extra_arg(args, data, "ill_sg_vowel"); if ill_sg_vowel == "" then error("Parameter \"ill_sg_vowel=\" cannot be empty.") end
	local ill_sg_vowel2 = get_extra_arg(args, data, "ill_sg_vowel2"); if ill_sg_vowel2 == "" then error("Parameter \"ill_sg_vowel2=\" cannot be empty.") end
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or params.base}
	stems["sg"]      = {params.base}
	stems["par_sg"]  = {params.base .. "t"}
	stems["ill_sg"]  = {params.base .. "h"}
	stems["pl"]      = {params.base .. "i"}
	stems["par_pl"]  = {params.base .. "it"}
	stems["ill_pl"]  = {params.base .. "ih"}
	
	inflection_type_is(data, 21, "rosé")
	process_stems(data, stems, params.a)
	
	if ill_sg_vowel then
		data.forms["ill_sg"] = {params.base .. "h" .. ill_sg_vowel .. "n"}
	end
	
	if ill_sg_vowel2 then
		table.insert(data.forms["ill_sg"], params.base .. "h" .. ill_sg_vowel2 .. "n")
	end
end

inflections["parfait"] = function(args, data)
	local params = get_params(args, 2)
	local ill_sg_vowel = get_extra_arg(args, data, "ill_sg_vowel"); if ill_sg_vowel == "" then error("Parameter \"ill_sg_vowel=\" is missing.") end
	local ill_sg_vowel2 = get_extra_arg(args, data, "ill_sg_vowel2"); if ill_sg_vowel2 == "" then error("Parameter \"ill_sg_vowel2=\" cannot be empty.") end
	
	local stems = {}
	stems["nom_sg"]  = {params.base}
	stems["sg"]      = {params.base .. "’"}
	stems["par_sg"]  = {params.base .. "’t"}
	stems["pl"]      = {params.base .. "’i"}
	stems["par_pl"]  = {params.base .. "’it"}
	stems["ill_pl"]  = {params.base .. "’ih"}
	
	inflection_type_is(data, 22, "parfait")
	process_stems(data, stems, params.a)
	
	data.forms["ill_sg"] = {params.base .. "’h" .. ill_sg_vowel .. "n"}
	
	if ill_sg_vowel2 then
		table.insert(data.forms["ill_sg"], params.base .. "h" .. ill_sg_vowel2 .. "n")
	end
end

inflections["tiili"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "i"}
	stems["sg"]      = {params.base .. "e"}
	stems["par_sg"]  = {params.base .. "t"}
	stems["pl"]      = {params.base .. "i"}
	
	inflection_type_is(data, 23, "tiili")
	process_stems(data, stems, params.a)
end

inflections["uni"] = function(args, data)
	local params = get_params(args, 2)
	local par_sg_a = get_extra_arg(args, data, "par_sg_a"); if par_sg_a and par_sg_a ~= "a" and par_sg_a ~= "ä" then error("Parameter \"par_sg_a=\" must be \"a\" or \"ä\".") end
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "i"}
	stems["sg"]      = {params.base .. "e"}
	stems["par_sg"]  = {params.base .. "t"}
	stems["pl"]      = {params.base .. "i"}
	stems["gen_pl"]  = {params.base .. "i", params.base .. "t"}
	
	inflection_type_is(data, 24, "uni")
	process_stems(data, stems, params.a)
	
	if par_sg_a then
		data.forms["par_sg"] = {}
		
		for _, stem in ipairs(stems["par_sg"]) do
			table.insert(data.forms["par_sg"], stem .. par_sg_a)
		end
	end
end

inflections["toimi"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "mi"}
	stems["sg"]      = {params.base .. "me"}
	stems["par_sg"]  = {params.base .. "nt", params.base .. "me"}
	stems["pl"]      = {params.base .. "mi"}
	stems["gen_pl"]  = {params.base .. "mi", params.base .. "nt"}
	
	inflection_type_is(data, 25, "toimi")
	process_stems(data, stems, params.a)
end

inflections["pieni"] = function(args, data)
	local params = get_params(args, 2)
	local par_sg_a = get_extra_arg(args, data, "par_sg_a"); if par_sg_a and par_sg_a ~= "a" and par_sg_a ~= "ä" then error("Parameter \"par_sg_a=\" must be \"a\" or \"ä\".") end
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "i"}
	stems["sg"]      = {params.base .. "e"}
	stems["par_sg"]  = {params.base .. "t"}
	stems["pl"]      = {params.base .. "i"}
	stems["gen_pl"]  = {params.base .. "t", params.base .. "i"}
	
	inflection_type_is(data, 26, "pieni")
	process_stems(data, stems, params.a)
	
	if par_sg_a then
		data.forms["par_sg"] = {}
		
		for _, stem in ipairs(stems["par_sg"]) do
			table.insert(data.forms["par_sg"], stem .. par_sg_a)
		end
	end
end

inflections["käsi"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "si"}
	stems["sg"]      = {params.base .. "te"}
	stems["sg_weak"] = {params.base .. "de"}
	stems["par_sg"]  = {params.base .. "tt"}
	stems["pl"]      = {params.base .. "si"}
	
	inflection_type_is(data, 27, "käsi", "t", "d")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "tten"}
end

inflections["kynsi"] = function(args, data)
	local params = get_params(args, 2, false, "n")
	local cons = mw.ustring.match(params.base, "[lnr]$")
	
	if not cons then error("Stem must end in \"l\", \"n\" or \"r\".") end
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "si"}
	stems["sg"]      = {params.base .. "te"}
	stems["sg_weak"] = {params.base .. cons .. "e"}
	stems["par_sg"]  = {params.base .. "tt"}
	stems["pl"]      = {params.base .. "si"}
	
	inflection_type_is(data, 28, "kynsi", cons .. "t", cons .. cons)
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "tten"}
end

inflections["lapsi"] = function(args, data)
	local params = get_params(args, 2, false, "p")
	if not mw.ustring.match(params.base, "[kp]$") then error("Stem must end in \"k\" or \"p\".") end
	local syncopated_stem = mw.ustring.sub(params.base, 1, -2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "si"}
	stems["sg"]      = {params.base .. "se"}
	stems["par_sg"]  = {syncopated_stem .. "st"}
	stems["pl"]      = {params.base .. "si"}
	stems["gen_pl"]  = {params.base .. "si", syncopated_stem .. "st"}
	
	inflection_type_is(data, 29, "lapsi")
	process_stems(data, stems, params.a)
end

inflections["veitsi"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "tsi"}
	stems["sg"]      = {params.base .. "tse"}
	stems["par_sg"]  = {params.base .. "st"}
	stems["pl"]      = {params.base .. "tsi"}
	
	inflection_type_is(data, 30, "veitsi")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "sten"}
end

inflections["kaksi"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "ksi"}
	stems["sg"]      = {params.base .. "hte"}
	stems["sg_weak"] = {params.base .. "hde"}
	stems["par_sg"]  = {params.base .. "ht"}
	stems["pl"]      = {params.base .. "ksi"}
	
	inflection_type_is(data, 31, "kaksi", "t", "d")
	process_stems(data, stems, params.a)
end

inflections["sisar"] = function(args, data)
	local params = get_params(args, 5, true)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or params.base .. params.weak .. params.final}
	stems["sg"]      = {params.base .. params.strong .. params.final .. "e"}
	stems["par_sg"]  = {params.base .. params.weak .. params.final .. "t"}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "i"}
	stems["gen_pl"]  = {params.base .. params.strong .. params.final .. "i", params.base .. params.weak .. params.final .. "t"}
	
	inflection_type_is(data, 32, "sisar", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["kytkin"] = function(args, data)
	local params = get_params(args, 5, true)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. params.weak .. params.final .. "n"}
	stems["sg"]      = {params.base .. params.strong .. params.final .. "me"}
	stems["par_sg"]  = {params.base .. params.weak .. params.final .. "nt"}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "mi"}
	stems["gen_pl"]  = {params.base .. params.strong .. params.final .. "mi", params.base .. params.weak .. params.final .. "nt"}
	
	inflection_type_is(data, 33, "kytkin", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["onneton"] = function(args, data)
	local no_gradation = get_extra_arg(args, data, "no_tt") == "1"
	local strong
	if no_gradation then
		strong = "t"
	else
		strong = "tt"
	end
	
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "t" .. params.o .. "n"}
	stems["sg"]      = {params.base .. strong .. params.o .. "m" .. params.a}
	stems["par_sg"]  = {params.base .. "t" .. params.o .. "nt"}
	stems["pl"]      = {params.base .. strong .. params.o .. "mi"}
	
	inflection_type_is(data, 34, "onneton", strong, "t")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "t" .. params.o .. "nten"}
end

inflections["lämmin"] = function(args, data)
	local params = get_params(args, 5, true)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. params.weak .. params.final .. "n"}
	stems["sg"]      = {params.base .. params.strong .. params.final .. "m" .. params.a}
	stems["par_sg"]  = {params.base .. params.weak .. params.final .. "nt"}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "mi"}
	
	inflection_type_is(data, 35, "lämmin", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.strong .. params.final .. "m" .. params.a .. "in"}
end

inflections["sisin"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "in"}
	stems["sg"]      = {params.base .. "imp" .. params.a}
	stems["sg_weak"] = {params.base .. "imm" .. params.a}
	stems["par_sg"]  = {params.base .. "int"}
	stems["pl"]      = {params.base .. "impi"}
	stems["pl_weak"] = {params.base .. "immi"}
	stems["gen_pl"]  = {params.base .. "impi", params.base .. "int"}
	
	inflection_type_is(data, 36, "sisin", "mp", "mm")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "imp" .. params.a .. "in"}
end

inflections["vasen"] = function(args, data)
	local params = get_params(args, 1)
	params.base = params.base .. "vase"
	params.a = "a"
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "n"}
	stems["sg"]      = {params.base .. "mp" .. params.a}
	stems["sg_weak"] = {params.base .. "mm" .. params.a}
	stems["par_sg"]  = {params.base .. "nt", params.base .. "mp" .. params.a}
	stems["pl"]      = {params.base .. "mpi"}
	stems["pl_weak"] = {params.base .. "mmi"}
	stems["gen_pl"]  = {params.base .. "mpi", params.base .. "nt"}
	
	inflection_type_is(data, 37, "vasen", "mp", "mm")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "mp" .. params.a .. "in"}
end

inflections["nainen"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "nen"}
	stems["sg"]      = {params.base .. "se"}
	stems["par_sg"]  = {params.base .. "st"}
	stems["pl"]      = {params.base .. "si"}
	stems["gen_pl"]  = {params.base .. "st", params.base .. "si"}
	
	inflection_type_is(data, 38, "nainen")
	process_stems(data, stems, params.a)
end

inflections["vastaus"] = function(args, data)
	local params = get_params(args, 2)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or params.base .. "s"}
	stems["sg"]      = {params.base .. "kse"}
	stems["par_sg"]  = {params.base .. "st"}
	stems["pl"]      = {params.base .. "ksi"}
	stems["gen_pl"]  = {params.base .. "st", params.base .. "ksi"}
	
	inflection_type_is(data, 39, "vastaus")
	process_stems(data, stems, params.a)
end

inflections["kalleus"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "s"}
	stems["sg"]      = {params.base .. "te"}
	stems["sg_weak"] = {params.base .. "de"}
	stems["par_sg"]  = {params.base .. "tt"}
	stems["pl"]      = {params.base .. "ksi"}
	
	inflection_type_is(data, 40, "kalleus", "t", "d")
	process_stems(data, stems, params.a)
end

inflections["vieras"] = function(args, data)
	local params = get_params(args, 5, true)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or (params.base .. params.weak .. params.final .. "s")}
	stems["sg"]      = {params.base .. params.strong .. params.final .. params.final}
	stems["par_sg"]  = {params.base .. params.weak .. params.final .. "st"}
	stems["ill_sg"]  = {params.base .. params.strong .. params.final .. params.final .. "se"}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. params.final .. "it"}
	stems["ill_pl"]  = {params.base .. params.strong .. params.final .. "isi"}
	
	inflection_type_is(data, 41, "vieras", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. params.weak .. params.final .. "sten"}
	data.forms["ill_pl"].rare = {params.base .. params.strong .. params.final .. "ihin"}
end

inflections["mies"] = function(args, data)
	local params = get_params(args, 1)
	local cap = get_extra_arg(args, data, "cap"); if cap == "" then nom_sg = nil end
	params.base = params.base .. (cap and "Mie" or "mie")
	params.a = "ä"
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "s"}
	stems["sg"]      = {params.base .. "he"}
	stems["par_sg"]  = {params.base .. "st"}
	stems["pl"]      = {params.base .. "hi"}
	stems["gen_pl"]  = {params.base .. "st", params.base .. "hi"}
	
	inflection_type_is(data, 42, "mies")
	process_stems(data, stems, params.a)
end

inflections["ohut"] = function(args, data)
	local params = get_params(args, 5, true)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. params.weak .. params.final .. "t"}
	stems["sg"]      = {params.base .. params.strong .. params.final .. "e"}
	stems["par_sg"]  = {params.base .. params.weak .. params.final .. "tt"}
	stems["pl"]      = {params.base .. params.strong .. params.final .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. params.final .. "it"}
	stems["ill_pl"]  = {params.base .. params.strong .. params.final .. "isi", params.base .. params.strong .. params.final .. "ih"}
	
	inflection_type_is(data, 43, "ohut", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["kevät"] = function(args, data)
	local params = get_params(args, 4, true)
	local vowel = params.a
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. params.weak .. vowel .. "t"}
	stems["sg"]      = {params.base .. params.strong .. vowel .. vowel}
	stems["par_sg"]  = {params.base .. params.weak .. vowel .. "tt"}
	stems["ill_sg"]  = {params.base .. params.strong .. vowel .. vowel .. "se"}
	stems["pl"]      = {params.base .. params.strong .. vowel .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. vowel .. "it"}
	stems["ill_pl"]  = {params.base .. params.strong .. vowel .. "isi"}
	
	inflection_type_is(data, 44, "kevät", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms["ill_pl"].rare = {params.base .. params.strong .. vowel .. "ihin"}
end

inflections["kahdeksas"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "s"}
	stems["sg"]      = {params.base .. "nte"}
	stems["sg_weak"] = {params.base .. "nne"}
	stems["par_sg"]  = {params.base .. "tt"}
	stems["pl"]      = {params.base .. "nsi"}
	
	inflection_type_is(data, 45, "kahdeksas", "nt", "nn")
	process_stems(data, stems, params.a)
end

inflections["tuhat"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. "t"}
	stems["sg"]      = {params.base .. "nte"}
	stems["sg_weak"] = {params.base .. "nne"}
	stems["par_sg"]  = {params.base .. "tt"}
	stems["pl"]      = {params.base .. "nsi"}

	inflection_type_is(data, 46, "tuhat", "nt", "nn")
	process_stems(data, stems, params.a)
	
	data.forms["gen_pl"].rare = {params.base .. "nten"}
end

inflections["kuollut"] = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems["nom_sg"]  = {params.base .. params.u .. "t"}
	stems["sg"]      = {params.base .. "ee"}
	stems["par_sg"]  = {params.base .. params.u .. "tt"}
	stems["ill_sg"]  = {params.base .. "eese"}
	stems["pl"]      = {params.base .. "ei"}
	stems["par_pl"]  = {params.base .. "eit"}
	stems["ill_pl"]  = {params.base .. "eisi", params.base .. "eih"}
	
	inflection_type_is(data, 47, "kuollut")
	process_stems(data, stems, params.a)
end

inflections["hame"] = function(args, data)
	local params = get_params(args, 4, true)
	local stem_vowel = get_extra_arg(args, data, "stem_vowel"); if stem_vowel == "" then stem_vowel = nil end
	stem_vowel = stem_vowel or "e"
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems["nom_sg"]  = {nom_sg or (params.base .. params.weak .. stem_vowel)}
	stems["sg"]      = {params.base .. params.strong .. stem_vowel .. stem_vowel}
	stems["par_sg"]  = {params.base .. params.weak .. stem_vowel .. "tt"}
	stems["ill_sg"]  = {params.base .. params.strong .. stem_vowel .. stem_vowel .. "se"}
	stems["pl"]      = {params.base .. params.strong .. stem_vowel .. "i"}
	stems["par_pl"]  = {params.base .. params.strong .. stem_vowel .. "it"}
	stems["ill_pl"]  = {params.base .. params.strong .. stem_vowel .. "isi", params.base .. params.strong .. stem_vowel .. "ih"}
	
	inflection_type_is(data, 48, "hame", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections["askel"] = function(args, data)
	local params = get_params(args, 5, true)
	local prefer_hame = get_extra_arg(args, data, "e") == "1"
	
	local stems_sisar = {}
	stems_sisar["sg"]      = {params.base .. params.strong .. params.final .. "e"}
	stems_sisar["nom_sg"]  = {params.base .. params.weak .. params.final}
	stems_sisar["par_sg"]  = {params.base .. params.weak .. params.final .. "t"}
	stems_sisar["ill_sg"]  = {params.base .. params.strong .. params.final .. "e"}
	stems_sisar["pl"]      = {params.base .. params.strong .. params.final .. "i"}
	stems_sisar["gen_pl"]  = {params.base .. params.strong .. params.final .. "i", params.base .. params.weak .. params.final .. "t"}
	stems_sisar["par_pl"]  = {params.base .. params.strong .. params.final .. "i"}
	stems_sisar["ill_pl"]  = {params.base .. params.strong .. params.final .. "i"}

	local stems_hame = {}
	stems_hame["sg"]      = {params.base .. params.strong .. params.final .. "ee"}
	stems_hame["nom_sg"]  = {params.base .. params.strong .. params.final .. "e"}
	stems_hame["par_sg"]  = {params.base .. params.strong .. params.final .. "ett"}
	stems_hame["ill_sg"]  = {params.base .. params.strong .. params.final .. "eese"}
	stems_hame["pl"]      = {params.base .. params.strong .. params.final .. "ei"}
	stems_hame["gen_pl"]  = {params.base .. params.strong .. params.final .. "eid", params.base .. params.strong .. params.final .. "eitt"}
	stems_hame["par_pl"]  = {params.base .. params.strong .. params.final .. "eit"}
	stems_hame["ill_pl"]  = {params.base .. params.strong .. params.final .. "eisi", params.base .. params.strong .. params.final .. "eih"}

	local stems = {}
	if prefer_hame then
		feed_list(stems, stems_hame)
		feed_list(stems, stems_sisar)
	else
		feed_list(stems, stems_sisar)
		feed_list(stems, stems_hame)
	end

	inflection_type_is(data, 49, "askel", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

-- Helper functions

-- joins data.words[...].forms to data.forms
function export.join_words(data, sep_supplier)
	local reorganized = {}
	local classes = {}
	
	-- reorganize from words[n].forms[case](.rare) to forms[case],words[n](.rare)
	for windex, word in ipairs(data.words) do
		table.insert(classes, word.class)
		for case, forms in pairs(word.forms) do
			if reorganized[case] == nil then
				reorganized[case] = {}
			end
			reorganized[case][windex] = {}
			for _, form in ipairs(forms) do
				table.insert(reorganized[case][windex], form)
			end
			if word.forms[case].rare then
				reorganized[case][windex].rare = {}
				for _, form in ipairs(word.forms[case].rare) do
					table.insert(reorganized[case][windex].rare, form)
				end
			end
		end
	end
	
	-- merge the forms with a Cartesian product to produce all possible combinations
	data.forms = {}
	for case, words in pairs(reorganized) do
		data.forms[case] = forms_cart_product(words, case, sep_supplier, classes)
	end

	if #data.words <= 1 then
		-- use title and categories of the sole word if there is only one
		data.title = data.words[1].title
		data.categories = data.words[1].categories
	else
		-- if there are multiple words, force nuoripari type
		data.title = make_kotus_title(51, "nuoripari")
		data.categories = {"Finnish nuoripari-type nominals"}
	end
	data.words = nil
end

local function cleanup_suffix(suffix)
	if suffix and mw.ustring.find(suffix, "_") then
		return mw.ustring.gsub(suffix, "_", " ")
	else
		return suffix	
	end
end

-- computes the Cartesian product of tables
function cart_product(words, depth)
	depth = depth or 1
	local prod = {}
	
	for _, val in ipairs(words[depth]) do
		if depth < #words then
			-- go over the next list
			for _, res in ipairs(cart_product(words, depth + 1)) do
				table.insert(prod, { val, unpack(res) })
			end
		else
			-- end of list, simply return the original
			table.insert(prod, { val })
		end
	end
	
	return prod
end

local function supplied_concat(list, sep_supplier)
	local result = ""
	local n = #list
	if n >= 1 then
		for i = 1, n - 1 do
			local v = mw.ustring.match(list[i], "^[aeiouyäö]")
			if v and mw.ustring.find(result, v .. "$") then
				result = result .. "-"
			end
			result = result .. list[i] .. sep_supplier(i)
		end
		
		local v = mw.ustring.match(list[n], "^[aeiouyäö]")
		if v and mw.ustring.find(result, v .. "$") then
			result = result .. "-"
		end
		
		result = result .. list[n]
	end
	return result
end

-- computes the Cartesian product of tables, also concats
function cart_product_concat(words, sep_supplier)
	local res = {}

	for _, combination in ipairs(cart_product(words)) do
		table.insert(res, supplied_concat(combination, sep_supplier))
	end

	return res
end

-- returns a bit mask (!) or nil
function get_rhyming_pattern(word, case, class)
	if class == "askel" then
		return nil
	end
	if case == "gen_pl" then
		if mw.ustring.match(word, "tten$") then
			return 2
		elseif mw.ustring.match(word, "ten$") then
			return 3
		else
			return 1
		end
	elseif case == "ill_sg" then
		if mw.ustring.match(word, "seen$") then
			return 1
		elseif mw.ustring.match(word, "hen$") then
			return 2
		end
	elseif case == "ill_pl" then
		if mw.ustring.match(word, "siin$") then
			return 1
		elseif mw.ustring.match(word, "hin$") then
			return 2
		end
	end
	return nil -- not applicable
end

function is_nonrhyming(form, case, classes)
	local expected = m_bit32.bnot(0) -- -1
	
	for i, word in ipairs(form) do
		local got = get_rhyming_pattern(word, case, classes[i])
		if got then
			expected = m_bit32.band(expected, got)
		end
		if expected == 0 then
			return true
		end
	end

	return false
end

-- computes the Cartesian product of tables, also concats
-- returns non-rhyming combinations as rare
function cart_product_concat_nonrhyming_rare(words, case, sep_supplier, classes)
	local res = {}
	local rare = {}
	local multichoice = 0
	local allow_pruning = false

	for _, position in ipairs(words) do
		if #position > 1 then
			multichoice = multichoice + 1
		end
	end

	allow_pruning = multichoice > 1

	for _, combination in ipairs(cart_product(words)) do
		local item = supplied_concat(combination, sep_supplier)
		if is_nonrhyming(combination, case, classes) then
			table.insert(rare, item)
		else
			table.insert(res, item)
		end
	end

	if #res < 1 then
		rare.rare = {}
		return rare
	end

	res.rare = rare
	return res
end

-- converts a list of words to extract the rare forms for ipairs
-- the number is interpreted bit-by-bit to decide which combination
-- to choose
function prepare_rare_tables(words, code)
	local result = {}
	
	for _, forms in ipairs(words) do
		-- replace with rare if bit is 1
		if m_bit32.band(code, 1) == 1 then
			table.insert(result, forms.rare or {})
		else
			table.insert(result, forms)
		end
		
		-- shift right to test next bit
		code = m_bit32.rshift(code, 1)
	end
	
	return result
end

-- copies all entries of source and inserts them to target
function merge_table(target, source)
	for _, value in ipairs(source) do
		table.insert(target, value)
	end
end

function merge_table_rare(target, source)
	for _, value in ipairs(source) do
		table.insert(target, value)
	end
	target.rare = source.rare
end

-- the Cartesian product of possible forms
function forms_cart_product(words, case, sep_supplier, classes)
	local result = {}
	result.rare = {}

	m_bit32 = require("bit32")
	
	-- merge possible non-rare forms
	merge_table_rare(result, cart_product_concat_nonrhyming_rare(words, case, sep_supplier, classes))
	
	-- merge possible rare forms
	-- for example, with two words:
	--        1 = rare A, common B
	--        2 = common A, rare B
	--        3 = rare A, rare B
	-- (2 ^ #words) - 1 == m_bit32.lshift(1,#words)-1
	-- (prepare_rare_tables actually takes out the rare forms)
	for i = 1,m_bit32.lshift(1,#words)-1 do
		merge_table(result.rare, cart_product_concat(prepare_rare_tables(words, i), sep_supplier))
	end
	
	-- if no rare forms, remove the table completely
	if #result.rare < 1 then
		result.rare = nil
	end
	return result
end

function make_word_possessive(args, data, poss, always_add)
	local pos = get_extra_arg(args, data, "pos"); if not pos or pos == "" then pos = "noun" end
	local par_nom_sg = get_extra_arg(args, data, "par_nom_sg") == "1"

	-- "no possessive forms exist" sentinel value
	if poss == "-" then
		return data.forms
	end

	if always_add or pos == "noun" then
		-- add possessive suffix
		if poss == "3" or poss == "3p" then
			poss = "3s" -- 3rd person forms are identical
		end
		if not poss_forms[poss] then
			error("Invalid poss value: '" .. p .. "'")
		end
		return make_poss_with_suffix(data.forms, data.stems, poss_forms[poss], "", poss_alt[poss], par_nom_sg)
	end
	return data.forms
end

function postprocess_word(args, data, gdata, always_add)
	local pos = get_extra_arg(args, data, "pos"); if not pos or pos == "" then pos = "noun" end
	local alwayspl = get_extra_arg(args, data, "alwayspl"); if alwayspl == "" then alwayspl = nil end
	
	if gdata.poss then
		data.forms = make_word_possessive(args, data, gdata.poss, always_add)
	elseif pos == "noun" and data.forms["com_pl"] then
		-- Add the possessive suffix to the comitative plural, if the word is a noun
		for key, subform in ipairs(data.forms["com_pl"]) do
			data.forms["com_pl"][key] = subform .. "en"
		end
		gdata.tagged_com_pl = true
	end
	
	if get_extra_arg(args, data, "gen_nom_sg") == "1" then
		data.forms["nom_sg"] = data.forms["gen_sg"]
	elseif get_extra_arg(args, data, "par_nom_sg") == "1" then
		data.forms["nom_sg"] = data.forms["par_sg"]
	end
	
	if gdata.poss then
		data.forms["ins_pl"] = nil
	end

	if alwayspl then -- [[Saint Vincent ja Grenadiinit]]
		for k, v in pairs(data.forms) do
			local k_sg = k:gsub("_pl", "_sg")
			if data.forms[k_sg] then
				data.forms[k_sg] = data.forms[k]
			end
		end
	end
end

function postprocess(args, data)
	local pos = args["pos"]; if not pos or pos == "" then pos = "noun" end
	local nosg = args["nosg"]; if nosg == "" then nosg = nil end
	local nopl = args["nopl"]; if nopl == "" then nopl = nil end
	local n = args["n"]; if n == "" then n = nil end
	local suffix = cleanup_suffix(args["suffix"]); if suffix == "" then suffix = nil end
	local appendix = args["appendix"]; if appendix == "" then appendix = nil end
	local has_ins_sg = args["has_ins_sg"]; if has_ins_sg == "" then has_ins_sg = nil end
	local has_no_nom = args["has_no_nom"]; if has_no_nom == "" then has_no_nom = nil end
	
	-- Add the possessive suffix to the comitative plural, if the word is a noun
	-- now done per word; see postprocess_word
	
	if nosg or n == "pl" then
		table.insert(data.categories, "Finnish pluralia tantum")
	end
	
	-- TODO: This says "nouns", but this module is also used for adjectives!
	if nopl or n == "sg" then
		table.insert(data.categories, "Finnish uncountable nouns")
	end
	
	if n == "csg" then          -- "chiefly singular"
		data.rare_plural = true
	end
	
	if not has_ins_sg then
		data.forms["ins_sg"] = nil
	end
	
	for key, form in pairs(data.forms) do
		-- Add suffix to forms
		for i, subform in ipairs(form) do
			subform = subform .. (suffix or "")
			form[i] = subform
		end
		
		if form.rare then
			for i, subform in ipairs(form.rare) do
				subform = subform .. (suffix or "")
				form.rare[i] = subform
			end
		end
		
		-- Do not show singular or plural forms for nominals that don't have them
		if ((nosg or n == "pl") and key:find("_sg$")) or ((nopl or n == "sg") and key:find("_pl$")) then
			form = nil
		end
		
		data.forms[key] = form
	end
	
	-- Check if the lemma form matches the page name
	local lemma = data.forms[(nosg or n == "pl") and "nom_pl" or "nom_sg"][1]
	if not appendix and lemma ~= data.pagename and not data.poss then
		--error("The lemma " .. lemma .. " does not match the page title. Check the parameters!")
		mw.addWarning("<i>Please check the fi-decl-... parameters!</i>")
		table.insert(data.categories, "Finnish entries with inflection not matching pagename")
	end
	
	data.is_appendix = appendix
	
	if has_no_nom then
		data.forms["nom_sg"] = nil
		data.forms["nom_pl"] = nil
	end
end

-- Make the table
function make_table(data, preview, title_override, collapse_class_override, accel_class_prefix, extra_classes)
	local rare_plural = data.rare_plural
	local note = rare_plural and "Plural forms of this word are not commonly used, but might be found in figurative uses, in some set phrases or in colloquial language." or nil

	local com_forms

	local function show_form(forms, code, no_rare)
		local form = forms[code]
		if not form then
			return "&mdash;"
		elseif type(form) ~= "table" then
			error("a non-table value was given in the list of inflected forms.")
		end
		
		local ret = {}
		local accel

		if rare_plural and code:find("_pl$") then
			-- plural is marginal
			for key, subform in ipairs(form) do
				table.insert(ret, "(''" .. make_link(subform) .. "'')")
			end
			
			if not no_rare and form.rare then
				for key, subform in ipairs(form.rare) do
					table.insert(ret, "(''" .. make_link(subform) .. "''" .. RARE .. ")")
				end
			end

			return table.concat(ret, "<br/>")
		end

		-- See [[Module talk:fi-nominals]].
		if code == "nom_sg" and not data.poss then
			accel = nil
		elseif code == "gen_sg" then
			accel = "gen//acc|s"
		elseif code == "nom_pl" then
			accel = "nom//acc|p"
		else
			accel = code:gsub("%f[^_](%a%a)$", {sg = "s", pl = "p"}):gsub("ins", "ist"):gsub("_", "|")
		end
		
		if accel_class_prefix and accel then
			accel = accel_class_prefix .. accel
		end

		for key, subform in ipairs(form) do
			table.insert(ret, make_link(subform, accel))
		end
		
		if not no_rare and form.rare then
			if accel then
				accel = 'rare-' .. accel
			end

			for key, subform in ipairs(form.rare) do
				table.insert(ret, make_link(subform, accel) .. RARE)
			end
		end
		
		return table.concat(ret, "<br/>")
	end
	
	local function repl(param)
		if param == "title" then
			if title_override then return title_override end
			return "[[Appendix:Finnish nominal forms|Inflection]] of " .. (data.is_appendix and make_link(data.pagename) or tag_term(data.pagename)) .. ( data.title and " (" .. data.title .. ")" or "")
		elseif param == "maybenote" then
			if note then
				return [=[
|- class="vsHide"
| colspan="4" class="fi-decl-maybenote" | ]=] .. note .. "\n"
			else
				return ""
			end
		elseif param == "com_forms" then
			return com_forms
		else
			local param2 = mw.ustring.match(param, "^(.-):c$")
			
			if param2 then
				return show_form(data.forms, param2, true)
			else
				return show_form(data.forms, param)
			end
		end
	end

	if not data.poss and data.tagged_com_pl then
		com_forms = 'colspan="2" | \'\'See the possessive forms below.\'\''
	else
		com_forms = mw.ustring.gsub('{{{com_sg}}} || {{{com_pl}}}', "{{{([a-z0-9_:]+)}}}", repl)
	end

	if preview then
		preview = [=[
|- class="vsShow"
! class="case-column" colspan="2" | nominative
| class="number-column" | {{{nom_sg:c}}}
| class="number-column" | {{{nom_pl:c}}}
|- class="vsShow"
! colspan="2" | genitive
| {{{gen_sg:c}}}
| {{{gen_pl:c}}}
|- class="vsShow"
! colspan="2" | partitive
| {{{par_sg:c}}}
| {{{par_pl:c}}}
|- class="vsShow"
! colspan="2" | illative
| {{{ill_sg:c}}}
| {{{ill_pl:c}}}

]=]
	else
		preview = ""
	end
	
	local wikicode = [=[
{| class="inflection-table fi-decl vsSwitcher ]=] .. (extra_classes or "") .. [=[" data-toggle-category="]=] .. (collapse_class_override or "declension") .. [=["
|-
! class="vsToggleElement" colspan="4" | {{{title}}}
]=] .. preview .. [=[
|- class="vsHide"
! class="case-column" colspan="2" |
! class="number-column" | singular
! class="number-column" | plural
|- class="vsHide"
! colspan="2" | nominative
| {{{nom_sg}}}
| {{{nom_pl}}}
|- class="vsHide"
! rowspan="2" | accusative
! <abbr title="The nominative accusative is used, for example, as the object of certain passives and imperatives. Click the Inflection link and see the section on accusatives for more information.">nom.</abbr>
| {{{nom_sg}}}
| rowspan="2" | {{{nom_pl}}}
|- class="vsHide"
! gen.
| {{{gen_sg}}}
|- class="vsHide"
! colspan="2" | genitive
| {{{gen_sg}}}
| {{{gen_pl}}}
|- class="vsHide"
! colspan="2" | partitive
| {{{par_sg}}}
| {{{par_pl}}}
|- class="vsHide"
! colspan="2" | inessive
| {{{ine_sg}}}
| {{{ine_pl}}}
|- class="vsHide"
! colspan="2" | elative
| {{{ela_sg}}}
| {{{ela_pl}}}
|- class="vsHide"
! colspan="2" | illative
| {{{ill_sg}}}
| {{{ill_pl}}}
|- class="vsHide"
! colspan="2" | adessive
| {{{ade_sg}}}
| {{{ade_pl}}}
|- class="vsHide"
! colspan="2" | ablative
| {{{abl_sg}}}
| {{{abl_pl}}}
|- class="vsHide"
! colspan="2" | allative
| {{{all_sg}}}
| {{{all_pl}}}
|- class="vsHide"
! colspan="2" | essive
| {{{ess_sg}}}
| {{{ess_pl}}}
|- class="vsHide"
! colspan="2" | translative
| {{{tra_sg}}}
| {{{tra_pl}}}
|- class="vsHide"
! colspan="2" | abessive
| {{{abe_sg}}}
| {{{abe_pl}}}
|- class="vsHide"
! colspan="2" | instructive
| {{{ins_sg}}}
| {{{ins_pl}}}
|- class="vsHide"
! colspan="2" | comitative
| {{{com_forms}}}
{{{maybenote}}}|}]=]
	return mw.ustring.gsub(wikicode, "{{{([a-z0-9_:]+)}}}", repl)
end

------------------------------------------
-- POSSESSIVE FORM GENERATION & DISPLAY --
------------------------------------------

local function prepare_possessive_list(forms)
	local res = {}
	for _, v in ipairs(forms) do
		table.insert(res, v)
	end
	if forms["rare"] then
		for _, v in ipairs(forms["rare"]) do
			table.insert(res, v)
			res[v] = "rare"
		end
	end
	return res
end

local function wrap_rare_forms(forms)
	local newforms = {}
	for case, subforms in pairs(forms) do
		local common = {}
		local rare = {}
		for _, v in ipairs(subforms) do
			if subforms[v] == "rare" then
				table.insert(rare, v)
			else
				table.insert(common, v)
			end
		end
		common.rare = rare
		newforms[case] = common
	end
	return newforms
end

local function make_possessives_from_stems(stems, suffix, extra_suffix)
	local pforms = {}
	for _, stem in pairs(stems) do
		table.insert(pforms, stem .. suffix .. extra_suffix)
	end
	return pforms
end

function make_poss_with_suffix(forms, stems, poss_suffix, extra_suffix, allow_alt, par_nom_sg)
	local result = {}
	local par_sg_a = false
	if poss_suffix:find("a") and mw.ustring.sub(forms["ine_sg"][1], -1) == "ä" then
		poss_suffix = mw.ustring.gsub(poss_suffix, "a", "ä")
	end
	if mw.ustring.sub(forms["ine_sg"][1], -1) ~= mw.ustring.sub(forms["par_sg"][1], -1) then
		par_sg_a = true
	end

	for k, v in pairs(forms_alt_ok) do
		if forms[k] then
			local suffix = poss_suffix
			if k == "par_sg" and par_sg_a and mw.ustring.find(suffix, "ä$") then
				suffix = mw.ustring.gsub(poss_suffix, "ä", "a")
			end

			result[k] = {}
			if k == "par_sg" and allow_alt then
				-- par_sg is a bit of an exception: it allows
				-- alt form if it doesn't end in two "aa"/"ää"
				local prepared = prepare_possessive_list(forms[k])
				for _, form in ipairs(prepared) do
					local modform = form
					if mw.ustring.sub(modform, -2, -2) ~= mw.ustring.sub(modform, -1) then
						local final = modform .. mw.ustring.sub(modform, -1) .. "n"
						table.insert(result[k], final)
						result[k][final] = prepared[form]
					end
				end
			elseif forms_alt_ok[k] and allow_alt then
				local prepared = prepare_possessive_list(forms[k])
				for _, form in ipairs(prepared) do
					local modform = form
					if k == "tra_sg" or k == "tra_pl" then
						modform = mw.ustring.sub(form, 1, -2) .. "e"
					end
					local final = modform .. mw.ustring.sub(modform, -1) .. "n"
					table.insert(result[k], final)
					result[k][final] = prepared[form]
				end
			end
			
			if k == "gen_sg" or k == "ins_sg" then
				result[k] = make_possessives_from_stems(stems["sg"], suffix, extra_suffix)
			elseif k == "ins_pl" then
				result[k] = make_possessives_from_stems(stems["pl"], suffix, extra_suffix)
			elseif forms_gen_ok[k] then
				local prepared = prepare_possessive_list(forms[k])
				for _, form in ipairs(prepared) do
					local tmp = form
					tmp = mw.ustring.sub(tmp, 1, -2)
					local final = tmp .. suffix .. extra_suffix
					table.insert(result[k], final)
					result[k][final] = prepared[form]
				end
			else
				local prepared = prepare_possessive_list(forms[k])
				for _, form in ipairs(prepared) do
					local modform = form
					if k == "tra_sg" or k == "tra_pl" then
						modform = mw.ustring.sub(form, 1, -2) .. "e"
					end
					local final = modform .. suffix .. extra_suffix
					table.insert(result[k], final)
					result[k][final] = prepared[form]
				end
			end
		end
	end

	-- nominative forms are (usually) identical to genitive singular
	result["nom_sg"] = result[par_nom_sg and "par_sg" or "gen_sg"]
	result["nom_pl"] = result["gen_sg"]
	return wrap_rare_forms(result)
end

local function serialize_args(args)
	local items = {}
	local entries = {}
	local max_number = 0

	for key, value in pairs(args) do
		if type(key) == "number" and key > 0 and key == math.floor(key) then
			items[key] = value
			max_number = math.max(key, max_number)
		else
			table.insert(entries, key .. "=" .. value)
		end
	end

	for i = 1,max_number,1 do
		items[i] = items[i] or ""
	end
	-- entries before items
	for i, v in ipairs(entries) do
		table.insert(items, i, v)
	end
	return table.concat(items, "|")
end

local poss_headings = {
	["1s"] = "first-person singular", ["2s"] = "second-person singular",
	["1p"] = "first-person plural", ["2p"] = "second-person plural",
	["3"] = "third-person",
}

local poss_accel = {
	["1s"] = "1|s", ["2s"] = "2|s", ["1p"] = "1|p", ["2p"] = "2|p", ["3"] = "3"
}

function make_possessive_table(pagename, args, pos, infl_title, get_forms)
	local note = nil

	if args["noposs"] then
		return ""
	elseif pos == "adj" then
		note = not (args["adjpossok"] or args["hideadjnote"]) and "'''[[Appendix:Finnish possessive suffixes#Adjectives|Rare]]'''. Only used with [[substantive adjective]]s." or ""
	elseif pos ~= "noun" then
		return ""
	end

	return "\n" .. make_possessive_table_internal(pagename, args, infl_title, note, get_forms)
end

function make_possessive_table_internal(pagename, args, infl_title, note, get_forms)
	local function show_subtable(code)
		return make_table(get_forms(code), false, poss_headings[code] .. " possessor", "possessive inflection", poss_accel[code] .. "|poss|form|of|", "fi-decl-poss-subtable")
	end
	
	local function repl(param)
		if param == "lemma" then
			return tag_term(pagename)
		elseif param == "info" then
			return " <small>(" .. infl_title .. ")</small>"
		elseif param == "maybenote" then
			if note then
				return [=[
|- class="vsHide"
| colspan="3" | ]=] .. note .. "\n"
			else
				return ""
			end
		else
			return show_subtable(param)
		end
	end
	
	local wikicode = [=[
{| class="inflection-table vsSwitcher fi-decl-poss" data-toggle-category="possessive inflection" cellspacing="1" cellpadding="2"
|- class="fi-decl-poss-header"
! class="vsToggleElement" colspan="3" | [[Appendix:Finnish possessive suffixes|Possessive forms]] of {{{lemma}}}{{{info}}}
{{{maybenote}}}|- class="vsHide"
|
{{{1s}}}
{{{2s}}}
{{{1p}}}
{{{2p}}}
{{{3}}}
|}]=]
	return mw.ustring.gsub(wikicode, "{{{([a-z0-9_:]+)}}}", repl)
end

export.inflections = inflections
return export