Documentation for this module may be created at Module:Sandbox/Χ/InfoboxMaker/doc

local ga = require('Module:Arguments').getArgs
local tt = require('Module:TableTools')
local ib = require('Module:Infobox').infobox
local ii = require('Module:InfoboxImage').InfoboxImage

local origArgs			-- Args received from the the initalizing module.
local ibArgs     = {}	-- Args sent to Module:Infobox
local categories = {}	-- Categories appended to the Infobox
local counter = {
	row = 1,
	subheader = 1,
	image = 1,
}
local params = {
	global = {
		'child',		-- properties
		'subbox',
		'name',
		'italic title',
		'decat',
		'bodyclass',	-- classes
		'titleclass',
		'aboveclass',
		'subheaderclass',
		'imageclass',
		'headerclass',
		'belowclass',
		'bodystyle',	-- styles
		'titlestyle',
		'abovestyle',
		'subheaderstyle',
		'captionstyle',
		'imagestyle',
		'headerstyle',
		'labelstyle',
		'datastyle',
		'belowstyle',
	},
	row = {
		'header',
		'label',
		'data',
		'class',
		'rowclass',
		'rowstyle',
		'rowcellstyle',
		'dataid',
		'labelid',
		'headerid',
		'rowid',
	},
	subheader = {
		'subheader',
		'subheaderrowclass',
		'subheaderstyle',
	},
	image = {
		'image',
		'size',
		'maxsize',
		'sizedefault',
		'alt',
		'border',
		'suppressplaceholder',
		'imagerowclass',
		'caption',
	},
}

local functions = {
	['or'] =	function (x)
					if type(x) ~= table then return x end
					for _, v in pairs(x) do
						if not isEmpty(v) then return v end
					end
				end,
	['ul'] =	function (x)
					if type(x) ~= table then return x end
					local l = mw.html.create('ul')
					for _, v in pairs(x) do
						if not isEmpty(v) then l:tag('li'):wikitext(v) end
					end
					return l
				end,
	['ol'] =	function (x)
					if type(x) ~= table then return x end
					local l = mw.html.create('ol')
					for _, v in pairs(x) do
						if not isEmpty(v) then l:tag('li'):wikitext(v) end
					end
					return l
				end,
	___ =		function (x) return type(x) == 'table' and x[1] or x end
}
setmetatable(functions, {__index = function (t) return t.___ end})

--[[
--------------------------------------------------------------------------------
------------------------------- Helper functions -------------------------------
--------------------------------------------------------------------------------
--]]

--[[
-- Checks whether x is nil, empty or false
--]]
local function isEmpty(x)
	return x == nil or x == '' or x == false or (type(x) == 'table' and tt.size(x) == 0)
end

--[[
-- Transforms x into a table
--]]
function totable(x)
	return type(x) == 'table' and x or {x}
end

--[[
-- Replaces one or multiple arguments with their associated value in origArgs.
-- There is an optional "n" suffix, intended to be a number.
--]]
local function argToVal(args, n)
	n = n and tostring(n) or ""
	if type(args) == 'table' then
		if n == '1' then
			for i, v in pairs(args) do
				args[i] = origArgs[v .. '1'] or origArgs[v]
			end
		else
			for i, v in pairs(args) do
				args[i] = origArgst[v .. n]
			end
		end
	elseif type(args) == 'string' then
		if n == '1' then
			args = origArgs[args .. '1'] or origArgs[args]
		else
			args = origArgs[args .. n]
		end
	end
	return args
end

--[[
-- Used to build "repeated" things.
-- Filters out values and arguments using their indices.
--]]
function filter(args, params, valIndex, argIndex)
	local newArgs = {}
	for _, param in ipairs(params) do
		local paramArg = args[param .. "Arg"]
		local paramVal = args[param]
		local default
		if type(paramVal) ~= 'table' then
			default = paramVal
			paramVal = {}
		end
		newArgs[param] = paramVal[valIndex] or argToVal(paramArg, argIndex) or default
		newArgs[param .. "Func"] = args[param .. "Func"]
	end
	return newArgs
end

--[[
-- Sets up unique elements like title, above, below, etc.
--]]
local function uniqueElement(prefix, args)
	if type(args) ~= 'table' then
		ibArgs[prefix] = args or ibArgs[prefix]
	else
		ibArgs[prefix]            = args[prefix] or ibArgs[prefix]
		ibArgs[prefix .. 'class'] = args.class or args[prefix .. 'class'] or ibArgs[prefix .. 'class']
		ibArgs[prefix .. 'style'] = args.style or args[prefix .. 'style'] or ibArgs[prefix .. 'style']
	end
end

--[[
-- Sets up incremental elements like images, subheader, and data rows
-- Optional n parameter.
--]]
function addElement(elem, args, n)
	local i
	if n == nil then
		i = tostring(counter[elem])
		counter[elem] = counter[elem] + 1
	else
		i = tostring(n)
	end
	for _, param in ipairs(params[elem]) do
		local func  = args[param .. "Func"]
		if type(func) ~= 'function' then func = functions[func] end
		local value = args[param] or argToVal(args[param .. "Arg"])
		ibArgs[param .. i] = pcall(function () func(value) end) and func(value) or value
	end
end

--[[
--------------------------------------------------------------------------------
------------------------------- Member functions -------------------------------
--------------------------------------------------------------------------------
--]]
local p = {}

function p.initialize(args)
	-- Modules using InfoboxMaker must first initialize it with a set of args.
	origArgs = args
end

--[[
-- Global styling and class assignments
--]]
function p.setup(args)
	for _, param in ipairs(params.global) do
		ibArgs[param] = args[param]
	end
end

function p.title(args)
	uniqueElement('title', args)
end
function p.above(args)
	uniqueElement('above', args)
end
function p.below(args)
	uniqueElement('below', args)
end

--[[
--  Adds a single subheader
--]]
function p.addSubheader(args)
	uniqueElement('subheader', {class=args.class or args.subheaderclass})
	addElement('subheader', args)
end

--[[
--  Adds a single image and caption
--]]
function p.addImage(args)
	uniqueElement('image', {
		class=args.class or args.imageclass,
		style=args.style or args.imagestyle,
	})
	local imgArgs = {}
	local i = tostring(counter.image)
	counter.image = counter.image + 1
	for _, param in ipairs(params.image) do
		local func  = args[param .. "Func"]
		if type(func) ~= 'function' then func = functions[func] end
		local value = args[param] or argToVal(args[param .. "Arg"])
		imgArgs[param] = pcall(function () func(value) end) and func(value) or value
	end
	ibArgs['image' .. i]         = ii{args=imgArgs}
	ibArgs['imagerowclass' .. i] = imgArgs.imagerowclass
	ibArgs['caption' .. i]       = imgArgs.caption
end

--[[
--  Adds a single category
--]]
function p.addCategory(catArgs)
	local name = catArgs.name or ""
	local key  = catArgs.key  or ""
	if not isEmpty(name) then
		if isEmpty(key) then
			table.insert(categories, "[[Category:" .. name .. "]]")
		else
			table.insert(categories, "[[Category:" .. name .. "|" .. key .. "]]")
		end
	end
end

--[[
--  Adds a single row
--]]
function p.addRow(rowArgs, n)
	local i
	if n == nil then
		i = tostring(counter.row)
		counter.row = counter.row + 1
	else
		i = tostring(n)
	end
	for _, param in ipairs(params.row) do
		local func  = rowArgs[param .. "Func"]
		if type(func) ~= 'function' then func = functions[func] end
		local value = rowArgs[param] or argToVal(rowArgs[param .. "Arg"])
		ibArgs[param .. i] = pcall(function () func(value) end) and func(value) or value
	end
	if ibArgs["header" .. i] or ibArgs["data" .. i] then
		return true
	else
		return false
	end
end

--[[
--  Adds multiple rows. Useful for numbered parameters.
--]]
function p.addRepeatedRow(rowArgs)
	local added = false
	-- First, determine how many rows will be needed
	local n = {}
	local m = 0
	for _, param in ipairs(params.row) do
		-- Number of paramArgs
		local paramArg = totable(rowArgs[param .. "Arg"])
		for _, prefix in pairs(paramArg) do
			for _, v in ipairs(tt.affixNums(origArgs, prefix)) do
				table.insert(n, v)
			end
		end
		-- Number of paramVals
		local paramVal = totable(rowArgs[param])
		m = math.max(m, #paramVal)
	end
	if isEmpty(n) then
		for i=1,m do table.insert(n, i) end
	else
		n = tt.removeDuplicates(n)
		table.sort(n)
	end
	
	-- Second, actually generate the rows
	if not isEmpty(n) then
		for i, j in ipairs(n) do
			local newRowArgs = filter(rowArgs, params.row, i, j)
			added = p.addRow(newRowArgs) or added
		end
	end
	return added
end

--[[
--  Adds a set of rows. The "first" row is added only if the rest is not empty.
--  Individual rows can actually be sections, repeated rows, or single rows.
--  First row must be a single row.
--]]
function p.addSection(sectionArgs)
	local rows  = sectionArgs.rows or {}
	local first = sectionArgs[1]   or {}
	local added = false
	local h = counter.row
	counter.row = counter.row + 1
	for _, row in ipairs(rows) do
		if     row.mode == 'section' then
			added = p.addSection(row) or added
		elseif row.mode == 'repeated'   then
			added = p.addRepeatedRow(row)    or added
		else
			added = p.addRow(row)     or added
		end
	end
	if added then
		p.addRow(first, h)
	end
	return added
end

--[[
-- Adds multiple sections sharing the same definition.
-- Useful with numbered parameters.
-- Only single rows are accepted.
--]]
function p.addRepeatedSection(sectionArgs)
	local rows  = sectionArgs.rows or {}
	local first = sectionArgs[1]   or {}
	local added = false
	local added_row = false
	local h
	
	-- First, determine how many rows will be needed
	local n = {}
	local m = 0
	for _, rowArgs in ipairs(rows) do
		for _, param in ipairs(params.row) do
			-- Number of paramArgs
			local paramArg = totable(rowArgs[param .. "Arg"])
			for _, prefix in pairs(paramArg) do
				for _, v in ipairs(tt.affixNums(origArgs, prefix)) do
					table.insert(n, v)
				end
			end
			-- Number of paramVals
			local paramVal = totable(rowArgs[param])
			m = math.max(m, #paramVal)
		end
	end
	if isEmpty(n) then
		for i=1,m do table.insert(n, i) end
	else
		n = tt.removeDuplicates(n)
		table.sort(n)
	end
	
	-- Second, actually generate the rows
	if not isEmpty(n) then
		for i, j in ipairs(n) do
			added_row = false
			h = counter.row
			counter.row = counter.row + 1
			for _, rowArgs in ipairs(rows) do
				local newRowArgs = filter(rowArgs, params.row, i, j)
				added_row = p.addRow(newRowArgs) or added
			end
			if added_row then
				local newFirst = filter(first, params.row, i, j)
				added = p.addRow(newFirst, h) or added_row or added
			end
		end
	end
	return added
end

--[[
-- Generate the infobox! Append categories if any.
--]]
function p.makeInfobox()
	return ib(ibArgs) .. mw.text.listToText(categories, '', '')
end

return p