local m_utilities = require("Module:utilities")
local m_links = require("Module:links")

local export = {}

local lang = require("Module:languages").getByCode("sv")
local vowel = "[aeiouyåäö]"
local consonant = "[bcdfghjklmnpqrstvwxz]"

local inflections = {}

local function postprocess(args, data)
	if args["noa"] then
		for key, form in pairs(data.forms) do
			if (key:find("actv$")) then
				data.forms[key] = nil
			end
		end

		-- default handling of participles for passive-only verbs
		if not args["prepart"] then
			for key, form in pairs(data.forms) do
				if key == "prespart" then
					for i, subform in ipairs(form) do
						form[i] = form[i] .. "s"
					end
				end
			end
		end
		if not args["pp"] then
			data.forms["pastpart"] = nil
		end
	else
		-- only deponent verbs have passive imperative forms
		data.forms["imppasv"]  = nil
		data.forms["impppasv"] = nil
	end

	if args["nop"] then
		for key, form in pairs(data.forms) do
			if (key:find("pasv$")) then
				data.forms[key] = nil
			end
		end
	end

	if args["noprepart"] then
		data.forms["prespart"] = nil
	end

	if args["nopp"] then
		data.forms["pastpart"] = nil
	end

	-- add particle to participles; it is added separately for the table later
	if args["particle"] or args["particle2"] then
		data.particle = args["particle"]
		table.insert(data.categories, "Swedish phrasal verbs")
		local part = args["particle2"] or args["particle"] or ""
		for key, form in pairs(data.forms) do
			if key == "prespart" or key == "pastpart" then
				for i, subform in ipairs(form) do
					form[i] = part .. form[i]
				end
			end
		end
	end
	
	if args["note"] and data.definitions and data.definitions:find("%)$") then
		data.definitions = mw.ustring.sub(data.definitions, 1, mw.ustring.len(data.definitions) - 1) .. ", " .. args["note"] .. ")"
	end
end

local function convert_to_accel(form)
	-- no accel for now
	return nil
end

-- Make the table
local function make_table(data)
	local function show_form(form, accel, show_particle)
		if not form then
			return "—"
		elseif type(form) ~= "table" then
			error("a non-table value was given in the list of inflected forms.")
		end

		local ret = {}
		local part = ""
		if show_particle and data.particle then
			part = " " .. m_links.full_link({lang = lang, term = data.particle})
		end

		for key, subform in ipairs(form) do
			table.insert(ret, m_links.full_link({lang = lang, term = subform, accel = accel and { ["form"] = accel } or nil}) .. part)
		end

		return table.concat(ret, ", ")
	end

	local function repl(param)
		if param == "head" then
			return data.particle and (data.head .. " " .. data.particle) or data.head
		elseif param == "definitions" then
			return data.definitions
		else
			return show_form(data.forms[param], convert_to_accel(param), param ~= "prespart" and param ~= "pastpart")
		end
	end

	local wikicode = [=[
<div class="NavFrame" style="max-width:54em;" data-toggle-category="inflection">
<div class="NavHead" >Conjugation of ]=] .. mw.getCurrentFrame():expandTemplate{title = "l-self", args = {"sv", data.head}} .. [=[ {{{definitions}}}</div>
<div class="NavContent" style="overflow:auto">
{| class="inflection-table" style="width: 100%; line-height: 125%; background-color: #F9F9F9; text-align: center; border: 1px solid #CCCCFF;" cellpadding="3" cellspacing="1"
|-
!
! colspan=2 style="width: 50%; background-color:#EFEFEF;" | Active
! colspan=2 style="width: 50%; background-color:#EFEFEF;" | Passive
|-
! style="background-color:#EFEFEF;" | Infinitive
| colspan=2 | {{{infactv}}}
| colspan=2 | {{{infpasv}}}
|-
! style="background-color:#EFEFEF;" | Supine
| colspan=2 | {{{supactv}}}
| colspan=2 | {{{suppasv}}}
|-
! style="background-color:#EFEFEF;" | Imperative
| colspan=2 | {{{impactv}}}
| colspan=2 | {{{imppasv}}}
|-
! style="background-color:#EFEFEF;" | ''Imper. plural''<sup>1</sup>
| colspan=2 | {{{imppactv}}}
| colspan=2 | {{{impppasv}}}
|-
!
! style="width: 25%; background-color:#EFEFEF;" | Present
! style="width: 25%; background-color:#EFEFEF;" | Past
! style="width: 25%; background-color:#EFEFEF;" | Present
! style="width: 25%; background-color:#EFEFEF;" | Past
|-
! style="background-color:#EFEFEF;" | Indicative
| {{{indpresactv}}}
| {{{indpastactv}}}
| {{{indprespasv}}}
| {{{indpastpasv}}}
|-
! style="background-color:#EFEFEF;" | ''Ind. plural''<sup>1</sup>
| {{{indppresactv}}}
| {{{indppastactv}}}
| {{{indpprespasv}}}
| {{{indppastpasv}}}
|-
! style="background-color:#EFEFEF;" | ''Subjunctive''<sup>2</sup>
| {{{subjpresactv}}}
| {{{subjpastactv}}}
| {{{subjprespasv}}}
| {{{subjpastpasv}}}
|-
!
! colspan=4 style="width: 100%; background-color:#EFEFEF;" | Participles
|-
! style="background-color:#EFEFEF;"| Present&nbsp;participle
| colspan=4 | {{{prespart}}}
|-
! style="background-color:#EFEFEF;" | Past&nbsp;participle
| colspan=4 | {{{pastpart}}}
|-
| colspan=5 style="text-align: left;" | <small><sup>1</sup> Archaic. <sup>2</sup> Dated. See [[Appendix:Swedish verbs#Dated and archaic forms|the appendix on Swedish verbs]].</small>
|}</div></div>]=]

	return mw.ustring.gsub(wikicode, "{{{([a-z0-9_]+)}}}", repl)
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
	
	if not inflections[infl_type] then
		error("Unknown inflection type '" .. infl_type .. "'")
	end

	local data = {head = args["head"] or mw.title.getCurrentTitle().text, definitions = "", forms = {}, categories = {}}

	-- Generate the forms
	inflections[infl_type](args, data)

	-- Postprocess
	postprocess(args, data)

	return make_table(data) .. m_utilities.format_categories(data.categories, lang)
end

local function split_multi(forms)
	if not forms or forms == "" or forms == "-" then
		return nil
	end
	return mw.text.split(forms, ",")
end

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

local function make_passive(forms)
	local pasv = {}
	for i, subform in ipairs(forms) do
		table.insert(pasv, subform .. "s")
	end
	return pasv
end

local weak_present_endings = {
	["r"] = "",
	["a"] = "r",
	["vw"] = "r",
	["fv"] = "ver",
	["mm"] = "mer",
	["nn"] = "ner"
}

local weak_past_endings = {
	["t"] = "",
	["d"] = "",
	["s"] = "t",
	["vl"] = "t",
	["vw"] = "dd"
}

local weak_sup_endings = {
	["t"] = "",
	["vw"] = "tt"
}

local weak_part_endings = {
	["vw"] = "ende"
}

local weak_prespasv_endings1 = {
	["nn"] = "ns"
}

local weak_prespasv_endings2 = {
	["fv"] = "ves",
	["mm"] = "mes",
	["nn"] = "nes"
}

local weak_subj_endings = {
	["mm"] = "me",
	["nn"] = "ne"
}

local weak_impp_endings = {
	["vw"] = "n",
	["fv"] = "ven",
	["mm"] = "men",
	["nn"] = "nen"
}

local function make_weak_present_form(stem, endc)
	local result = {stem .. (weak_present_endings[endc] or "er")}
	if endc == "dj" then
		table.insert(result, stem .. "jer")
	end
	return result
end

local function make_weak_prespasv_form(stem, endc)
	if endc == "s" then
		return {stem .. "es"}
	end
	local result = {stem .. (weak_prespasv_endings1[endc] or "s")}
	if endc ~= "vw" and endc ~= "a" then
		table.insert(result, stem .. (weak_prespasv_endings2[endc] or "es"))
	end
	if endc == "dj" then
		table.insert(result, stem .. "jes")
	end
	return result
end

inflections["weak"] = function(args, data)
	data.definitions = "(weak)"
	table.insert(data.categories, "Swedish weak verbs")

	local head = data.head
	-- remove passive final -s to make forms correct
	if head:find("s$") and args["noa"] then
		head = mw.ustring.sub(head, 1, mw.ustring.len(head) - 1)
	end
	local basic = args[1] or head
	local past = args[2] or basic
	local full = args["full"] or head
	local endc = args["end"] or (args[1] and "" or "a")
	local bare = basic
	local vowelstem = mw.ustring.find(basic, vowel .. "$")
	if endc == "a" then
		bare = mw.ustring.sub(bare, 1, mw.ustring.len(bare) - 1)
		vowelstem = false
	end

	data.forms["infactv"]      = {head}
	data.forms["indpresactv"]  = make_weak_present_form(basic, endc)
	data.forms["indpastactv"]  = args["past"] and split_multi(args["past"]) or {past .. (weak_past_endings[endc] or "d") .. "e"}
	data.forms["supactv"]      = args["sup"] and split_multi(args["sup"]) or {past .. (weak_sup_endings[endc] or "t")}
	data.forms["impactv"]      = args["imp"] and split_multi(args["imp"]) or {basic .. ((endc == "nn") and "n" or "")}
	data.forms["imppactv"]     = args["impp"] and split_multi(args["impp"]) or {bare .. (weak_impp_endings[endc] or "en")}
	data.forms["prespart"]     = args["prepart"] and split_multi(args["prepart"]) or {full .. (not args["full"] and weak_part_endings[endc] or "nde")}
	data.forms["pastpart"]     = args["pp"] and split_multi(args["pp"]) or {past .. (weak_past_endings[endc] or "d")}
	data.forms["indppresactv"] = data.forms["infactv"]
	data.forms["indppastactv"] = data.forms["indpastactv"]
	data.forms["subjpresactv"] = args["sbj"] and split_multi(args["sbj"]) or (vowelstem and data.forms["infactv"] or {bare .. (weak_subj_endings[endc] or "e")})
	data.forms["subjpastactv"] = data.forms["indpastactv"]

	data.forms["infpasv"]      = make_passive(data.forms["infactv"])
	data.forms["indprespasv"]  = make_weak_prespasv_form(basic, endc)
	data.forms["indpastpasv"]  = make_passive(data.forms["indpastactv"])
	data.forms["suppasv"]      = make_passive(data.forms["supactv"])
	data.forms["imppasv"]      = make_passive(data.forms["impactv"])
	data.forms["impppasv"]     = make_passive(data.forms["imppactv"])
	data.forms["indpprespasv"] = data.forms["infpasv"]
	data.forms["indppastpasv"] = data.forms["indpastpasv"]
	data.forms["subjprespasv"] = make_passive(data.forms["subjpresactv"])
	data.forms["subjpastpasv"] = data.forms["indpastpasv"]
end

local strong_pres_endings = {
	["r"] = "",
	["vw"] = "r",
	["fv"] = "ver",
	["mm"] = "mer",
	["nn"] = "ner"
}

local strong_sup_endings = {
	["fv"] = "vit",
	["mm"] = "mit",
	["nn"] = "nit"
}

local strong_pastpart_endings = {
	["fv"] = "ven",
	["mm"] = "men",
	["nn"] = "nen"
}

local strong_subj_endings = {
	["vw"] = "",
	["fv"] = "ve",
	["mm"] = "me",
	["nn"] = "ne"
}

local strong_impp_endings = {
	["vw"] = "n",
	["fv"] = "ven",
	["mm"] = "men",
	["nn"] = "nen"
}

local function make_strong_ppl_stem(class, endc, past, sup)
	local base
	if class == "3" or class == "4" then
		base = sup
	else
		base = past
	end
	if endc == "mm" then
		base = base .. "m"
	elseif endc == "nn" then
		base = base .. "n"
	end
	return base
end

local function make_strong_prespasv_form(stem, endc)
	if endc == "s" then
		return {stem .. "es"}
	end
	local result = {stem .. (weak_prespasv_endings1[endc] or "s")}
	if endc ~= "vw" and endc ~= "a" then
		table.insert(result, stem .. (weak_prespasv_endings2[endc] or "es"))
	end
	if endc == "dj" then
		table.insert(result, stem .. "jes")
	end
	return result
end

local function get_final_vowel(stem)
	return mw.ustring.match(stem, ".*(" .. vowel .. ")")
end

local function try_detect_strong_class(pres, past, sup, endc)
	local presv = get_final_vowel(pres)
	local pastv = get_final_vowel(past)
	local supv = get_final_vowel(sup)

	if supv == "i" or supv == "ä" or supv == "e" then
		if supv == "i" and pastv == "e" and presv == "i" then
			return "1"
		elseif (pastv == "a" or pastv == "å") and (presv == "e" or presv == "i" or presv == "ä") then
			return "5"
		end
	elseif supv == "u" then
		if pastv == "ö" and (presv == "u" or presv == "y") then
			return "2"
		elseif pastv == "a" then
			if presv == "i" then
				return "3"
			elseif presv == "e" or presv == "ä" then
				if pres:find(consonant .. consonant .. "$") then
					return "3"
				else
					return "4"
				end
			end
		end
	elseif supv == "a" or supv == "å" then
		if supv == "a" and pastv == "o" and presv == "a" then
			return "6"
		elseif (pastv == "ö" or pastv == "ä") and (presv == "å" or presv == "a") then
			return "7"
		end
	end

	return nil
end

inflections["strong"] = function(args, data)
	table.insert(data.categories, "Swedish strong verbs")

	local head = data.head
	-- remove passive final -s to make forms correct
	if head:find("s$") and args["noa"] then
		head = mw.ustring.sub(head, 1, mw.ustring.len(head) - 1)
	end
	local endc = args["end"] or ""
	local pres = args[1] or error("At least 3 arguments required for strong verb inflections (missing present stem)")
	local past = args[2] or error("At least 3 arguments required for strong verb inflections (missing past stem)")
	local sup = args[3]
	-- it's fine to not specify the supine stem if supine and pp forms are manually given
	if not sup and (not args["sup"] or not (args["pp"] or args["nopp"])) then
		error("At least 3 arguments required for strong verb inflections (missing supine stem; alternatively specify supine and past participle forms manually)")
	elseif not sup then
		sup = mw.ustring.sub(args["sup"], 1, mw.ustring.len(args["sup"]) - 1)
	end
	local class = args["class"] or try_detect_strong_class(pres, past, sup, endc)
	local full = args["full"] or (head .. (endc == "vw" and "e" or ""))
	local ppl = args["ppl"] or make_strong_ppl_stem(class, endc, past, sup)
		
	if class then
		table.insert(data.categories, "Swedish class " .. class .. " strong verbs")
		data.definitions = "(class " .. class .. " strong)"
	else
		data.definitions = "(strong)"
	end

	if class == "5" then -- for class 5, replace final -a- with -å- in plural/subjunctive stem
		ppl = mw.ustring.gsub(ppl, "(.*)a(.*)", "%1å%2")
	end
	if endc == "fv" and ppl:find("f$") then
		ppl = ppl .. "v"
	end

	data.forms["infactv"]      = {head}
	data.forms["indpresactv"]  = {pres .. (strong_pres_endings[endc] or "er")}
	data.forms["indpastactv"]  = {past}
	data.forms["supactv"]      = args["sup"] and split_multi(args["sup"]) or {sup .. (strong_sup_endings[endc] or "it")}
	data.forms["impactv"]      = args["imp"] and split_multi(args["imp"]) or {pres}
	data.forms["imppactv"]     = args["impp"] and split_multi(args["impp"]) or {pres .. (strong_impp_endings[endc] or "en")}
	data.forms["prespart"]     = args["prepart"] and split_multi(args["prepart"]) or {full .. "nde"}
	data.forms["pastpart"]     = args["pp"] and split_multi(args["pp"]) or {sup .. (strong_pastpart_endings[endc] or "en")}
	data.forms["indppresactv"] = args["pl"] and split_multi(args["pl"]) or data.forms["infactv"]
	data.forms["indppastactv"] = {ppl .. "o"}
	data.forms["subjpresactv"] = args["sbj"] and split_multi(args["sbj"]) or {pres .. (strong_subj_endings[endc] or "e")}
	data.forms["subjpastactv"] = args["psbj"] and split_multi(args["psbj"]) or {ppl .. "e"}

	data.forms["infpasv"]      = make_passive(data.forms["infactv"])
	data.forms["indprespasv"]  = make_strong_prespasv_form(pres, endc)
	data.forms["indpastpasv"]  = make_passive(data.forms["indpastactv"])
	data.forms["suppasv"]      = make_passive(data.forms["supactv"])
	data.forms["imppasv"]      = make_passive(data.forms["impactv"])
	data.forms["impppasv"]     = make_passive(data.forms["imppactv"])
	data.forms["indpprespasv"] = make_passive(data.forms["indppresactv"])
	data.forms["indppastpasv"] = make_passive(data.forms["indppastactv"])
	data.forms["subjprespasv"] = make_passive(data.forms["subjpresactv"])
	data.forms["subjpastpasv"] = make_passive(data.forms["subjpastactv"])
end

inflections["irreg"] = function(args, data)
	table.insert(data.categories, "Swedish irregular verbs")
	data.definitions = "(irregular)"

	data.forms["infactv"]      = split_multi(args[1])
	data.forms["indpresactv"]  = split_multi(args[2])
	data.forms["indpastactv"]  = split_multi(args[3])
	data.forms["supactv"]      = split_multi(args[4])
	data.forms["impactv"]      = split_multi(args[5])
	data.forms["imppactv"]     = split_multi(args["impp"])
	data.forms["prespart"]     = split_multi(args[6])
	data.forms["pastpart"]     = split_multi(args[7])
	data.forms["indppresactv"] = split_multi(args["pl"])
	data.forms["indppastactv"] = split_multi(args["ppl"])
	data.forms["subjpresactv"] = split_multi(args["sbj"])
	data.forms["subjpastactv"] = split_multi(args["psbj"])
end

return export