Module:Fr-headword

local export = {}
local pos_functions = {}
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub

local lang = require("Module:languages").getByCode("fr")
		
local suffix_categories = {
	["adjectives"] = true,
	["adverbs"] = true,
	["nouns"] = true,
	["verbs"] = true,
}

-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
	local retval = rsubn(term, foo, bar)
	return retval
end

local function track(page)
	require("Module:debug").track("fr-headword/" .. page)
	return true
end

local function add_suffix(list, suffix)
	local newlist = {}
	for _, item in ipairs(list) do
		local form
		if suffix == "s" then
			if rfind(item, "[sx]$") then
				form = item
			elseif rfind(item, "al$") then
				form = rsub(item, "al$", "aux")
			else
				form = item .. suffix
			end
		elseif suffix == "e" then
			if rfind(item, "e$") then
				form = item
			elseif rfind(item, "en$") then
				form = item .. "ne"
			elseif rfind(item, "er$") then
				form = rsub(item, "er$", "ère")
			elseif rfind(item, "el$") then
				form = item .. "le"
			elseif rfind(item, "et$") then
				form = item .. "te"
			elseif rfind(item, "on$") then
				form = item .. "ne"
			elseif rfind(item, "ieur$") then
				form = item .. "e"
			elseif rfind(item, "teur$") then
				form = rsub(item, "teur$", "trice")
			elseif rfind(item, "eu[rx]$") then
				form = rsub(item, "eu[rx]$", "euse")
			elseif rfind(item, "if$") then
				form = rsub(item, "if$", "ive")
			elseif rfind(item, "c$") then
				form = rsub(item, "c$", "que")
			else
				form = item .. suffix
			end
		else
			form = item .. suffix
		end
		table.insert(newlist, form)
	end
	return newlist
end

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
	local PAGENAME = mw.title.getCurrentTitle().text
	
	local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
	
	local params = {
		["head"] = {list = true, default = ""},
		["suff"] = {type = "boolean"},
	}

	if mw.ustring.find(PAGENAME, " ") then
		track("space")
	end

	if pos_functions[poscat] then
		for key, val in pairs(pos_functions[poscat].params) do
			params[key] = val
		end
	end

	local parargs = frame:getParent().args
	local args = require("Module:parameters").process(parargs, params)
	local data = {lang = lang, pos_category = poscat, categories = {}, heads = args["head"], genders = {}, inflections = {}, categories = {}}
	
	if args["suff"] then
		data.pos_category = "suffixes"
		
		if suffix_categories[poscat] then
			local singular_poscat = poscat:gsub("s$", "")
			table.insert(data.categories, lang:getCanonicalName() .. " " .. singular_poscat .. "-forming suffixes")
		else
			error("No category exists for suffixes forming " .. poscat .. ".")
		end
	end
	
	if pos_functions[poscat] then
		pos_functions[poscat].func(args, data)
	end
	
	return require("Module:headword").full_headword(data)
end

local allowed_genders = {
	["m"] = true,
	["f"] = true,
	["m-p"] = true,
	["f-p"] = true,
	["m-s"] = true,
	["f-s"] = true,
}

pos_functions["nouns"] = {
	params = {
		[1] = {},
		["g"] = {list = true},
		[2] = {list = true},
		["f"] = {list = true},
		["m"] = {list = true},
		["dim"] = {list = true},
		},
	func = function(args, data)
		local PAGENAME = mw.title.getCurrentTitle().text
		
		local function default_plural()
			if mw.ustring.find(PAGENAME, 'x$') then
				track("default-x")
			end
			if mw.ustring.find(PAGENAME, 'z$') then
				track("default-z")
			end
			if mw.ustring.find(PAGENAME,'[sxz]$') then
				return PAGENAME
			elseif mw.ustring.find(PAGENAME,'[ae]u$') then
				return "x"
			elseif mw.ustring.find(PAGENAME,'al$') then
				return mw.ustring.sub(PAGENAME, 1, -3) .. 'aux'
			else
				return "s"
			end
		end

		-- Gather genders
		local function insert_gender(g)
			if g == "mf" then
				table.insert(data.genders, "m")
				table.insert(data.genders, "f")
			else
				table.insert(data.genders, g)
			end
		end
		insert_gender(args[1])
		for _, g in ipairs(args.g) do
			insert_gender(g)
		end

		-- Gather all the plural parameters from the numbered parameters.
		local plurals = args[2]
		plurals.label = "plural"
		plurals.accel = "plural-form-of"
		plurals.request = true
		
		-- Gather all the feminine parameters
		local feminines = args["f"]
		feminines.label = "feminine"
		
		-- Gather all the masculine parameters
		local masculines = args["m"]
		masculines.label = "masculine"
		
		-- Add categories for genders
		if #data.genders == 0 then
			table.insert(data.genders, "?")
		end
		
		local mode = nil
		
		for _, g in ipairs(data.genders) do
			if g == "m-p" or g == "f-p" then
				mode = "p"
			end

			if g == "?" and mw.title.getCurrentTitle().nsText == "Template" then
				-- allow unknown gender in template example
			elseif g and g ~= "" and not allowed_genders[g] then
				error("Unrecognized French gender: " .. g)
			end

			if g == "m" or g == "m-p" or g == "m-s" then
				table.insert(data.categories, "French masculine nouns")
			elseif g == "f" or g == "f-p" or g == "f-s" then
				table.insert(data.categories, "French feminine nouns")
			end
		end
		
		-- Decide how to show the plurals
		mode = mode or plurals[1]
		
		-- Plural is not attested
		if mode == "!" then
			table.insert(data.inflections, {label = "plural not attested"})
			table.insert(data.categories, "French nouns with unattested plurals")
		-- Plural-only noun, doesn't have a plural
		elseif mode == "p" then
			table.insert(data.inflections, {label = "plural only"})
			table.insert(data.categories, "French pluralia tantum")
		else
			-- Plural is unknown
			if mode == "?" then
				table.remove(plurals, 1)  -- Remove the mode parameter
			-- Uncountable noun; may occasionally have a plural
			elseif mode == "-" then
				table.remove(plurals, 1)  -- Remove the mode parameter
				table.insert(data.categories, "French uncountable nouns")
			
				-- If plural forms were given explicitly, then show "usually"
				if #plurals > 0 then
					track("count-uncount")
					table.insert(data.inflections, {label = "usually [[Appendix:Glossary#uncountable|uncountable]]"})
					table.insert(data.categories, "French countable nouns")
				else
					table.insert(data.inflections, {label = "[[Appendix:Glossary#uncountable|uncountable]]"})
				end
			-- Mixed countable/uncountable noun, always has a plural
			elseif mode == "~" then
				table.remove(plurals, 1)  -- Remove the mode parameter
				table.insert(data.inflections, {label = "[[Appendix:Glossary#countable|countable]] and [[Appendix:Glossary#uncountable|uncountable]]"})
				table.insert(data.categories, "French uncountable nouns")
				table.insert(data.categories, "French countable nouns")
			
				-- If no plural was given, add a default one now
				if #plurals == 0 then
					table.insert(plurals, default_plural())
				end
			-- The default, always has a plural
			else
				table.insert(data.categories, "French countable nouns")
				
				-- If no plural was given, add a default one now
				if #plurals == 0 then
					table.insert(plurals, default_plural())
				end
			end
			
			-- Process the plural forms
			for i, pl in ipairs(plurals) do
				if pl == "*" then
					pl = PAGENAME
				elseif pl == "s" then
					pl = PAGENAME .. "s"
				elseif pl == "x" then
					pl = PAGENAME .. "x"
				end

				-- mw.title.new() can return nil if there are weird chars in
				-- the plural
				local newtitle = mw.title.new(pl)
				if newtitle and not newtitle.exists then
					table.insert(data.categories, "French nouns with missing plurals")
				end
				
				plurals[i] = pl
			end
	
			-- Add the plural forms
			if mode ~= "-" or #plurals > 0 then
				table.insert(data.inflections, plurals)
			end
		end
		
		-- Add the feminine forms
		if #feminines > 0 then
			table.insert(data.inflections, feminines)
			
			for _, f in ipairs(feminines) do
				if not mw.title.new(f).exists then
					table.insert(data.categories, "French nouns with missing forms")
				end
			end
		end
		
		-- Add the masculine forms
		if #masculines > 0 then
			table.insert(data.inflections, masculines)
			
			for _, m in ipairs(masculines) do
				if not mw.title.new(m).exists then
					table.insert(data.categories, "French nouns with missing forms")
				end
			end
		end

		-- Handle diminutives
		if #args.dim > 0 then
			local dims_infl = mw.clone(args.dim)
			dims_infl.label = "diminutive"
			dims_infl.accel = "diminutive-form-of"
			table.insert(data.inflections, dims_infl)
		end
	end
}

pos_functions["adjectives"] = {
	params = {
		[1] = {},
		["inv"] = {},
		["m2"] = {},
		["onlyg"] = {},
		["f"] = {list = true},
		["mp"] = {list = true},
		["fp"] = {list = true},
		["p"] = {list = true},
		["current"] = {list = true},
		["comp"] = {list = true},
		["sup"] = {list = true},
		},
	func = function(args, data)
		local PAGENAME = mw.title.getCurrentTitle().text
		if args.onlyg == "p" or args.onlyg == "m-p" or args.onlyg == "f-p" then
			table.insert(data.categories, "French pluralia tantum")
		end
		if args.onlyg == "s" or args.onlyg == "f-s" or args.onlyg == "f-s" then
			table.insert(data.categories, "French singularia tantum")
		end
		if args.onlyg then
			table.insert(data.categories, "French defective adjectives")
		end
		if args.onlyg == "p" then
			table.insert(data.inflections, {label = "plural only"})
			if args[1] ~= "mf" then
				-- Handle feminine plurals
				if #args.fp > 0 then
					local fplurals_infl = mw.clone(args.fp)
					fplurals_infl.label = "feminine plural"
					fplurals_infl.accel = "feminine-plural-form-of"
					table.insert(data.inflections, fplurals_infl)
				end
			end
		elseif args.onlyg == "s" then
			table.insert(data.inflections, {label = "singular only"})
			if not (args[1] == "mf" or #args.f == 0 and rfind(PAGENAME, "e$")) then
				-- Handle feminines
				local feminines = #args.f > 0 and args.f or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "e")
				local feminines_infl = mw.clone(feminines)
				feminines_infl.label = "feminine singular"
				feminines_infl.accel = "feminine-singular-form-of"
				table.insert(data.inflections, feminines_infl)
			end
		elseif args.onlyg == "m" then
			table.insert(data.genders, "m")
			table.insert(data.inflections, {label = "masculine only"})
			-- Handle masculine plurals
			local mplurals = #args.mp > 0 and args.mp or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "s")
			local mplurals_infl = mw.clone(mplurals)
			mplurals_infl.label = "masculine plural"
			mplurals_infl.accel = "masculine-plural-form-of"
			table.insert(data.inflections, mplurals_infl)
		elseif args.onlyg == "f" then
			table.insert(data.genders, "f")
			table.insert(data.inflections, {label = "feminine only"})
			-- Handle feminine plurals
			local fplurals = #args.fp > 0 and args.fp or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "s")
			local fplurals_infl = mw.clone(fplurals)
			fplurals_infl.label = "feminine plural"
			fplurals_infl.accel = "feminine-plural-form-of"
			table.insert(data.inflections, fplurals_infl)
		elseif args.onlyg then
			table.insert(data.genders, args.onlyg)
			table.insert(data.inflections, {label = "defective"})
		else
			-- Gather genders
			local gender = args[1]
			-- Default to mf if base form ends in -e and no feminine,
			-- feminine plural or gender specified
			if not gender and #args.f == 0 and #args.fp == 0 and rfind(PAGENAME, "e$") then
				gender = "mf"
			end
			
			if #args.current > 0 then
				track("adj-current")
			end
			
			if args.inv then
				table.insert(data.inflections, {label = "invariable"})
			end
			
			-- Handle plurals of mf adjectives
			local plurals = #args.p > 0 and args.p or {PAGENAME .. "s"}
			if not args.inv and gender == "mf" then
				local plurals_infl = mw.clone(plurals)
				plurals_infl.label = "plural"
				plurals_infl.accel = "plural-form-of"
				table.insert(data.inflections, plurals_infl)
			end
			
			if not args.inv and gender ~= "mf" then
				-- Handle case of special masculine singular before vowel
				if args.m2 then
					local masc_before_vowel = {args.m2}
					masc_before_vowel.label = "masculine singular before vowel"
					masc_before_vowel.accel = "masculine-singular-form-of"
					table.insert(data.inflections, masc_before_vowel)
				end
				
				-- Handle feminines
				local feminines = #args.f > 0 and args.f or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "e")
				local feminines_infl = mw.clone(feminines)
				feminines_infl.label = "feminine singular"
				feminines_infl.accel = "feminine-singular-form-of"
				table.insert(data.inflections, feminines_infl)
				
				-- Handle masculine plurals
				local mplurals = #args.mp > 0 and args.mp or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "s")
				local mplurals_infl = mw.clone(mplurals)
				mplurals_infl.label = "masculine plural"
				mplurals_infl.accel = "masculine-plural-form-of"
				table.insert(data.inflections, mplurals_infl)
				
				-- Handle feminine plurals
				local fplurals = #args.fp > 0 and args.fp or add_suffix(feminines, "s")
				local fplurals_infl = mw.clone(fplurals)
				fplurals_infl.label = "feminine plural"
				fplurals_infl.accel = "feminine-plural-form-of"
				table.insert(data.inflections, fplurals_infl)
			end
		end
		
		-- Handle comparatives
		if #args.comp > 0 then
			local comps_infl = mw.clone(args.comp)
			comps_infl.label = "comparative"
			comps_infl.accel = "comparative-form-of"
			table.insert(data.inflections, comps_infl)
		end
		
		-- Handle superlatives
		if #args.sup > 0 then
			local sups_infl = mw.clone(args.sup)
			sups_infl.label = "superlative"
			sups_infl.accel = "superlative-form-of"
			table.insert(data.inflections, sups_infl)
		end
	end
}

return export