local p = {}

-- -------------------------------------------------------------------------------

Reads the override{} table in Module:Lang/data and renders a wiki table of private tags and their associated languages


local function private_tags (frame)
	local override_t = mw.loadData ('Module:Lang/data').override;				-- get the override table
	
	local private_t = {};

	for tag, lang in pairs (override_t) do
		if tag:find ('%-x%-') then
			table.insert (private_t, table.concat )
		end
	end
	table.sort (private_t)

	table.insert ;
	table.insert 
	table.insert 

	return table.concat (private_t) .. '\n|}';
--	return ''
--error (mw.dumpObject (private_t))
end

-- -----------------------------------------------------------------------

Reads the content of the template and extracts the parameters from for display on the template's
documentation page.


local function lang_xx_settings(frame)
	local page = mw.title.makeTitle	-- get a page object for this page in 'Template:' namespace
	if not page then
		return ''																-- TODO: error message?
	end

	local content = page:getContent											-- get unparsed content
	if not page then
		return ''																-- TODO: error message?
	end

	local out = {}

	local params
	local style

	if content:match('{{%s*#invoke:%s*[Ll]ang%s*|[^|]+|[^}]+}}') or content:match('{{%s*#invoke:%s*[Ll]ang/sandbox%s*|[^|]+|[^}]+}}') then			-- if this template uses Module:Lang
		params = content:match('{{%s*#invoke:%s*[Ll]ang%s*|[^|]+(|[^}]+)}}') or content:match('{{%s*#invoke:%s*[Ll]ang/sandbox%s*|[^|]+(|[^}]+)}}')	-- extract the #invoke:'s parameters
		if not params then 
			return ''															-- there should be at least one or the template/module won't work TODO: error message?
		end
		table.insert	-- start a wikitable
		for k, v in params:gmatch('%s*|%s*([^%s=]+)%s*=%s*([^%s|]+)') do		-- get the parameter names (k) and values (v)
			if 'label' == k then												-- special case for labels because spaces and pipes
				v = params:match('label%s*=%s*(%[%^%+%]%])') or params:match('label%s*=%s*([^|\n]+)') or 'missing label'
			end
			table.insert(out, table.concat)						-- make rudimentary wikitable entries
		end

		style = content:match('lang_xx_([^|]+)')
		return table.concat({table.concat, '\n|-\n|colspan="2"|style: ', style, '\n|-\n|}'})	-- add inter-row markup and close the wikitable and done
	else
		return ''																-- does not use Module:Lang so abandon quietly
	end
end

-- --------------------------  --------------------------

Reads the content of the template to determine if this template uses Module:Lang.
Returns the index of the substring ' parser functions.


local function uses_module(frame)
	local page = mw.title.makeTitle	-- get a page object for this page in 'Template:' namespace
	if not page then
		return ''																-- TODO: error message?
	end

	local content = page:getContent											-- get unparsed content
	if not page then
		return ''																-- TODO: error message?
	end

	return content:find(' template. The correct category is located at: :%s.",
	["NO_CATEGORY_TITLE_FOUND"] = "No language category found for **%s.**" .. error_assistance,
	["NOT_VALID_CATEGORY_FORMAT"] = "**%s** is not a a valid category title." .. error_assistance,
	["NOT_VALID_LANGUAGE_CODE"] = "%s is not a valid ISO 639 or IETF language name." .. error_assistance,

local strings = {
	["ERROR_CATEGORY"] = "Category:Lang and lang-xx template errors",
	["ERROR_SPAN"] = '',
	["PURGE_DIV"] = '',
	["SEE_ALSO"] = "\n==See also==",
	["SEE_ALSO_ITEM"] = "* :%s",

-- --------------------------  --------------------------

Create an error message.
Does not place page in error category if args.nocat is used.
Does not categorize in parent cateogory if used in category namespace (usually for /testcases).


local function make_error
	table.insert(layout, string.format)

	if not nocat then
		table.insert
	end

	if mw.title.getCurrentTitle.nsText == "Category" then
		table.insert
	end

	return table.concat(layout)
end

-- --------------------------  --------------------------

Generates a language link for the correct style.

Collective languages use the name_from_tag value,
while other languages use a display name of "x-language".


local function get_language_link
	local lang_module = require('Module:Lang')
	-- Is a language collective?
	if language_name:find('languages') then
		return lang_module.name_from_tag
	else
		return lang_module.name_from_tag({language_code, link = "yes", label = lang_module.name_from_tag({language_code}) .. "-language"})
	end
end

-- --------------------------  --------------------------

Generates a consistent style See also section for

If is converted, it should also use it.


local function get_see_also_section
	local see_also_section = {}

	for _, category_name in pairs(language_categories) do
		local category = mw.title.new(string.format, 14)
 		if category and page_title ~= category.text and category.exists then
			table.insert(see_also_section, string.format)
		end
	end

	table.sort(see_also_section)
	table.insert

	if table.getn(see_also_section) == 1 then
		return ""
	else
		return table.concat
	end
end

-- --------------------------  --------------------------

Generates the Template:Hidden category template.

This function is separate from the get_top_section function
as this should be used in both error categories and valid categories.


local function get_hidden_category_template(frame)
	return frame:expandTemplate{title = 'Hidden category'}
end

-- --------------------------  --------------------------

Generates a consistent top maintenance template section which consists of:
-- Template:Possibly empty category
-- Template:Purge


local function get_top_section(frame)
	local top_section = {}
	if mw.site.stats.pagesInCategory == 0 then
		table.insert
	else
		table.insert
	end

	local purge_module = require('Module:Purge')
	table.insert(top_section, string.format(strings["PURGE_DIV"], purge_module._main({"Purge page cache"})))

	return table.concat
end

-- --------------------------  --------------------------

Generates a consistent non-text section which consists of:
-- Template:CatAutoTOC
-- A see also section
-- 
-- Categorization in parent category


local function get_bottom_section
	local bottom_section = {}
	table.insert
	table.insert

	if mw.title.getCurrentTitle.nsText == "Category" then
		table.insert
		table.insert
	end

	return table.concat
end

-- --------------------------  --------------------------

This function implements .


local non_english_language_text_strings = {
	["LINE1"] = "This category contains articles with %s%s text. The primary purpose of these categories is to facilitate manual or automated checking of text in other languages.",
	["LINE2"] = "This category should only be added with the %s family of templates, never explicitly.",
	["LINE3"] = 'For example %s, which wraps the text with .%s',
	["LINE3_EXTRA"] = " Also available is %s which displays as %s.",
	["IN_SCRIPT"] = " (in %s)",
	["EXAMPLE_DEFAULT_TEXT"] = "text in %s language here",
	["PARENT_CATEGORY"] = "Category:Articles containing non-English-language text",
	["TEMPLATE"] = "Lang",

local function non_english_language_text_category(frame)
	local page = mw.title.getCurrentTitle
	local args = require('Module:Arguments').getArgs(frame)
	-- args.test is used for /testcases
	if args.test then
		page = mw.title.new(args.test)
	end

	-- Naming style: Articles with text from the Berber languages collective
	local page_title_modified = page.text
	local split_title = "%%s"
	local part1 = ""
	local part2 = ""

	if page_title_modified:find('Articles with text in') then
		-- Naming style: Category:Articles with text from Afro-Asiatic languages (as currently implemented in Module:lang)
		part1, part2 = language_categories["LANGUAGES_COLLECTIVE_TEXT"]:match(split_title)
	elseif page_title_modified:find('explicitly cited') then
		part1, part2 = language_categories["ENGLISH"]:match(split_title)
	else
		-- Naming style: Category:Articles containing French-language text
		part1, part2 = language_categories["LANGUAGE_TEXT"]:match(split_title)
	end

	page_title_modified = page_title_modified:gsub
	page_title_modified = page_title_modified:gsub
	local language_name = page_title_modified

	local layout = {}
	table.insert(layout, get_hidden_category_template(frame))
	local parent_category = non_english_language_text_strings["PARENT_CATEGORY"]

	if language_name == page.text then
		-- Error: Category title format not supported.
		return make_error(string.format, layout, parent_category, args.nocat)
	end

	local lang_module = require('Module:Lang')
	local language_code = lang_module._tag_from_name({language_name})	

	if language_code:find('[Ee]rror') then
		-- Error: Language code not found in database.
		return make_error(string.format, layout, parent_category, args.nocat)
	end

	local correct_language_category_title = lang_module._category_from_tag({language_code})
	if correct_language_category_title:find('[Ee]rror') then
		-- Error: No category title found for language code.
		return make_error(string.format, layout, parent_category, args.nocat)
	end

	local current_category_title = page.prefixedText
	if current_category_title ~= correct_language_category_title then
		-- Error: The current title used is not in the supported format. TODO: can this still be reached?
		return make_error(
			string.format,
			layout, parent_category, args.nocat)
	end

	table.insert(layout, get_top_section(frame))

	local script_text = ""
	if args.script then
		script_text = string.format
	end

	local language_link = get_language_link
	table.insert(layout, string.format)

	local lang_template = frame:expandTemplate{title = 'Tl', args = {non_english_language_text_strings["TEMPLATE"]}}
	table.insert(layout, string.format)

	local language_code_link = lang_module._name_from_tag
	local example_default_text = string.format
	local example_text = args.example or example_default_text
	local lang_template_example = frame:expandTemplate{title = 'Tlx', args = {non_english_language_text_strings["TEMPLATE"], language_code_link, example_text}}
	local lang_x = mw.title.makeTitle

	local line3_extra = ""
	if lang_x.exists then
		local lang_x_template_example = frame:expandTemplate{title = 'Tlx', args = {lang_x.text, example_text}}
		local lang_x_template = frame:expandTemplate{title = lang_x.text, args = {example_text}}
		line3_extra = string.format
	end

	table.insert(layout, string.format)
	local see_also_section = get_see_also_section

	local bottom = get_bottom_section
	return table.concat .. bottom
end

-- --------------------------  --------------------------

This function implements .


local non_english_language_sources_strings = {
	["LINE1"] = "This is a tracking category for articles that use %s to identify %s sources.",
	["PARENT_CATEGORY"] = "Category:Articles with non-English-language sources",
	["TEMPLATE"] = "In lang",

local function non_english_language_sources_category(frame)
	local page = mw.title.getCurrentTitle
	local args = require('Module:Arguments').getArgs(frame)
	-- args.test is used for /testcases
	if args.test then
		page = mw.title.new(args.test)
	end

	local page_title = page.text
	local language_code = page_title:match('%(([%a%-]+)%)')
	local language_name = require('Module:Lang')._name_from_tag({language_code})

	local layout = {}
	table.insert(layout, get_hidden_category_template(frame))
	local parent_category = non_english_language_sources_strings["PARENT_CATEGORY"]

	local correct_language_category_title = require('Module:In lang')._in_lang
	if correct_language_category_title == "" then
		-- Error: No category title found for language code.
		return make_error(string.format, layout, parent_category, args.nocat)
	end

	local current_category_title = page.prefixedText
	if correct_language_category_title ~= current_category_title then
		-- Error: The current title used is not in the supported format.
		return make_error(
			string.format,
			layout, parent_category, args.nocat)
	end

	local language_link = get_language_link
	local text = string.format

	table.insert(layout, get_top_section(frame))	
	table.insert
	local see_also_section = get_see_also_section

	local bottom = get_bottom_section
	return table.concat .. bottom
end

-- --------------------------  --------------------------

This function implements .


local non_english_language_cs1_text_strings = {
	["LINE1"] = "This is a tracking category for CS1 citations that use the parameter %s to identify a source in %s. Pages in this category should only be added by CS1 templates and Module:Citation/CS1.",
	["PARENT_CATEGORY"] = "Category:CS1 foreign language sources", -- #TODO change to "Articles with non-english CS1 language sources" or "CS1 non-English language sources"

	--"This is a tracking category for CS1 citations that use the parameter %s to hold a citation title that uses %s characters and contains the language prefix . Pages in this category should only be added by CS1 templates and Module:Citation/CS1.",
	--"Category:CS1 uses foreign language script",

	-- "This is a tracking category for CS1 citations that use the parameter %s. Pages in this category should only be added by CS1 templates and Module:Citation/CS1.",
	-- "to identify a source in %s.",
	-- "to hold a citation title that uses %s characters and contains the language prefix .",

local function non_english_language_cs1_sources_category(frame)
	local page_title_object = mw.title.getCurrentTitle
	local page_title = page_title_object.text
	local language_code = page_title:match('%(([%a%-]+)%)')
	local language_name = require('Module:Lang')._name_from_tag({language_code})
	local layout = {}
	table.insert(layout, get_hidden_category_template(frame))
	local see_also_section = ""
	local parameter_doc = frame:expandTemplate{title = 'para', args = {"language", language_code}}
	table.insert(layout, get_top_section(frame))
	table.insert(layout, string.format)
	local see_also_section = get_see_also_section
	local bottom = get_bottom_section
	return table.concat .. bottom
end

-- --------------------------  -------------------------- 


local function compare_by_keys2											-- local function used by table.sort
	return a[2] < b[2]															-- ascending sort by code
end

local function compare_by_keys											-- local function used by table.sort
	return a[1] < b[1]															-- ascending sort by code
end

-- Used by testcases_iso_code_to_name
local function get_language_code_table_from_code(args)
	local entry = {}
	--if args.override_table[args.language_code] then
	--	table.insert -- :gsub fails here
	--else
		table.insert
--	end
	return entry
end

-- Used by testcases_name_from_tag
local function get_language_code_and_name_table_from_code(args)
	local entry = {}
	if args.override_table[args.language_code] then
		table.insert
		local language_code, _ = args.override_table[args.language_code][1]:gsub
		table.insert
	else
		table.insert
		table.insert
	end
	return entry
end

-- Used by testcases_category_from_tag
local function get_language_code_and_category_table_from_code(args)
	local entry = {}
	table.insert
	table.insert(entry, args.test_function({args.language_code}))
	return entry
end

-- Used by testcases_iso_name_to_code and testcases_tag_from_name
local function get_language_name_and_code_table_from_code(args)
	local entry = {}
	if args.override_table[args.language_code] then
		table.insert			-- only the first name when there are multiples
		table.insert
	else
		table.insert								-- only the first name when there are multiples
		table.insert
	end
	return entry
end

local function get_table
	local table_of_language_name_and_code_tables = {}

	local override_table_name = "override"
	if iso_number then
		override_table_name = "override_" .. iso_number
	end
	local override_table = require("Module:Language/data/ISO_639_override/sandbox")[override_table_name]

	-- For most ISO 639s.
	if range then
		for language_code, language_names in pairs(language_table) do
			if language_code:find(range) then
				table.insert(table_of_language_name_and_code_tables, table_function({
					override_table = override_table,
					language_code = language_code,
					language_names = language_names,
					test_function = test_function,
					language_table = language_table
			end
		end
	-- For ISO 639-1.
	elseif length then
		for language_code, language_names in pairs(language_table) do
			if language_code:len == 2 then
				table.insert(table_of_language_name_and_code_tables, table_function({
					override_table = override_table,
					language_code = language_code,
					language_names = language_names,
					test_function = test_function,
					language_table = language_table
			end
		end
	-- For general /testcases.
	else
		for language_code, language_names in pairs(language_table) do
				table.insert(table_of_language_name_and_code_tables, table_function({
					override_table = override_table,
					language_code = language_code,
					language_names = language_names,
					test_function = test_function,
					language_table = language_table
		end
	end
	return table_of_language_name_and_code_tables
end

local function get_undabbed_table
	local undabbed_language_table = {}											-- for this test, ISO 639-3 language name disambiguators must be removed; un-dabbed names go here
	for language_code, language_names in pairs(language_list) do
		-- For most ISO 639s.
		if range then
			if language_code:find(range) then
				undabbed_language_table[language_code] = language_names[1]:gsub	-- undab and save only the first name; ignore all other names assigned to a code
			end
		-- For ISO 639-1.
		elseif length then
			if language_code:len == 2 then
				undabbed_language_table[language_code] = language_names[1]:gsub	-- undab and save only the first name; ignore all other names assigned to a code
			end
		-- For general /testcases.
		else
			undabbed_language_table[language_code] = language_names[1]:gsub
		end
	end
	return undabbed_language_table
end

-- --------------------------  --------------------------

Entry point for the various category_from_tag testcases.

Build a table of test patterns where each entry in the table is a table with two members:

- "Expected" column value is the category name according to Module:Lang.
- "Actual" column value is the result of .

TODO: Currently not working.


local function testcases_category_from_tag
	local cat_from_tag_function = require('Module:Lang')._category_from_tag	
	local language_tables = get_table
	table.sort

	self:preprocess_equals_preprocess_many
end

-- --------------------------  --------------------------

Entry point for the various name_from_tag testcases.

Build a table of test patterns where each entry in the table is a table with two members:

- "Expected" column value is the .
- "Actual" column value is the result of sandbox version .


local function testcases_name_from_tag
	local undabbed_language_table = get_undabbed_table
	local language_tables = get_table
	table.sort

	self:preprocess_equals_preprocess_many
end

-- --------------------------  --------------------------

Entry point for the various tag_from_name testcases.

Build a table of test patterns where each entry in the table is a table with two members:

- "Expected" column value is the .
- "Actual" column value is the result of sandbox version .

TODO: Currently not working.


local function testcases_tag_from_name
	local language_tables = get_table
	table.sort

	local ordered_table = {}
	table.sort(unordered_table)
	for _, key in ipairs(unordered_table) do
		table.insert
	end

	self:preprocess_equals_preprocess_many
end

-- --------------------------  --------------------------

Entry point for the various iso_code_to_name testcases.

Build a table of test patterns where each entry in the table is a table with one member:

- "Expected" column value is the result of the live version of .
- "Actual" column value is the result of sandbox version .


local function testcases_iso_code_to_name
	local language_tables = get_table
	table.sort

	self:preprocess_equals_preprocess_many
end

-- --------------------------  --------------------------

Entry point for the various iso_name_to_code testcases.

Build a table of test patterns where each entry in the table is a table with two members:

- "Expected" column value is the .
- "Actual" column is value the result of .


local function testcases_iso_name_to_code
	local language_tables = get_table
	table.sort
	self:preprocess_equals_preprocess_many
end

------------------------------------------------------------------------------------------

adds items to the list of items in §See also section of Template:Lang-x/doc; Evaluates single positional parameter
which is a comma-separated list of items including list markup.



local function see_also (frame)
	if nil == frame.args[1] or '' == frame.args[1] then							-- if empty, ...
		return;																	-- ... return nothing
	end

	return frame:preprocess (frame.args[1]:gsub );				-- preprocess so any templates are rendered before saving and done
end

-- ------------------------- -----------------------------------------


return {
	lang_xx_settings = lang_xx_settings,
	uses_module = uses_module,
	see_also = see_also,
	non_english_language_text_category = non_english_language_text_category,
	non_english_language_sources_category = non_english_language_sources_category,
	non_english_language_cs1_sources_category = non_english_language_cs1_sources_category,
	private_tags = private_tags,

	-- Module:Lang testcases
	testcases_category_from_tag = testcases_category_from_tag,
	testcases_name_from_tag = testcases_name_from_tag,
	testcases_tag_from_name = testcases_tag_from_name,

	-- Module:ISO 639 name testcases
	testcases_iso_code_to_name = testcases_iso_code_to_name,
	testcases_iso_name_to_code = testcases_iso_name_to_code,