وحدة:Infobox ship

require('strict');

local utilities = require ('Module:WPSHIPS_utilities');
local infobox = require ('Module:Infobox').infobox;
local infobox_ship_flag = utilities._infobox_ship_flag;
local ship_name_format = utilities._ship_name_format;
local synonym_check = utilities._synonym_check;
local unbulleted_list = utilities._unbulleted_list;

local get_args = require ('Module:Arguments').getArgs;

local data = mw.loadData ('Module:Infobox_ship/data');


--[[--------------------------< L I N E _ I T E M S >-----------------------------------------------------------------------------

spin through <params_t> sequence.  Get parameter name and matching infobox label.  Look in <args_t> for parameter name.  When parameter
name has a value, add infobox label and parameter value to the infobox table <infobox_ship_t>.  <i> identifies where label and data
enumerators begin.

]]

local function line_items (args_t, params_t, infobox_ship_t, i, frame)
	for _, v in ipairs (params_t) do											-- v is a sequence table with parameter name and associated infobox label
		if args_t[v[1]] then													-- if parameter has a value
			infobox_ship_t['label' .. i] = v[2];								-- add the label
			infobox_ship_t['data' .. i] = unbulleted_list (args_t[v[1]]);		-- and add the parameter value as data
			i = i + 1;															-- bump the enumerator
		end
	end
end


--[[--------------------------< A D D _ W A R N I N G >--------------------------------------------------------

for unknown parameters, add a message in the edit-preview message box

<template> is the name of the enclosing infobox ship or one of the subtemplates
<k> is the parameter name
<v> is the parameter value

]]

local function add_warning (template, k, v)
	v = v:gsub (data.stripmarker, '');											-- suppress stripmarkers in the warning message
	mw.addWarning (string.format (data.warning_fmt_str, data.warn_span_style, data.warn_code_style, template, template, data.warn_code_style, k, v));
end


--[[--------------------------< U N K N O W N _ P A R A M S _C H E C K >---------------------------------------

check parameters supplied in <template> against known parameters for that template.  Emit preview warning when
a parameter is unknown.

Empty unknown parameters are not identified because Module:Arguments removes blank parameters by default.

]]

local function unknown_params_check (args_t, known_params_t, template)
	local has_unknown_params = false;

	for k, v in pairs (args_t) do
		if 'string' == type (k) then
			local param = k:gsub ('%d+$', '#');									-- for enumerated parameters, replace enumerator with '#'
			if not known_params_t[param] then
				add_warning (template, k, v);									-- add warning message when <param> is not known for <template>
				has_unknown_params = data.cat_this_namespace;					-- add unknown params category when infobox is rendered; mainspace and draftspace only
			end
		else																	-- here when <k> not a string (likely a positional parameter)
			add_warning (template, k, v);										-- add warning message when <param> is not known for <template>
			has_unknown_params = data.cat_this_namespace;						-- add unknown params category when infobox is rendered; mainspace and draftspace only
		end
	end
	return has_unknown_params;
end


--[[--------------------------< F I N A L _ R E N D E R I N G >------------------------------------------------

final rendering for the various subtemplates

]]

local function final_rendering (infobox_ship_t, frame, has_unknown_params, template)
	if 'yes' ~= infobox_ship_t.child then										-- when not 'yes', we are in stand-alone mode
		infobox_ship_t.bodyclass = 'mainbox';									-- use basic infobox css for stand-alone mode

		return table.concat ({
				frame:extensionTag ('templatestyles', '', {src='Module:Infobox ship/styles.css'}),				-- apply templatestyles
				infobox (infobox_ship_t),										-- render the infobox
				(has_unknown_params and data.categories_t.unknown) or ''		-- add category for any unknown parameters
			});
	else
		infobox_ship_t.label100 = '__1B0X_5H1P__';
		infobox_ship_t.data100 = template
		return frame:expandTemplate ({title='Infobox', args = infobox_ship_t});	-- return a rendering of this infobox
	end
end


--[[--------------------------< D I S P L A Y T I T L E _ M A K E >--------------------------------------------

account for namespace when creating {{DIPSLAYSTYLE}} magic word

]]

local function DISPLAYTITLE_make (display_title)
	local namespace_name = data.namespace_number ~= 0 and (data.namespace_name .. ':') or '';	-- no namespace prefix for articles in mainspace

	return string.format ('{{DISPLAYTITLE:%s%s}}', namespace_name, display_title);	-- construct magic word
end


--[[--------------------------< I N F O B O X _ S H I P _ B E G I N >------------------------------------------

fill infobox table parameters 'bodystyle' and 'title' (infobox caption); auto-article title formatting ({{DISPLAYTITLE}})

]]

local function infobox_ship_begin (args_t, infobox_ship_t, frame)
	local name = mw.title.getCurrentTitle().text;

	infobox_ship_t['bodyclass'] = 'mainbox';

	if args_t.infobox_caption then
		if 'yes' == args_t.infobox_caption then									-- format article title as infobox caption
			infobox_ship_t.title = ship_name_format ({name=name, adj='off', showerrs=args_t.showerrs, sclass=args_t.sclass});
		elseif 'nodab' == args_t.infobox_caption then							-- format article title without disambiguation as infobox caption
			infobox_ship_t.title = ship_name_format ({name=name, dab='none', showerrs=args_t.showerrs});
		else																	-- use value supplied in |infobox_caption= as infobox caption
			infobox_ship_t.title = args_t.infobox_caption;
		end
	else
		infobox_ship_t.title = '';												-- to allow for possible displaytitle concatenation
	end

	if args_t.display_title then
		if 'ital' == args_t.display_title then									-- use {{italic title}} template
			infobox_ship_t.title = infobox_ship_t.title .. require ('Module:italic title')._main ({});	-- {{italic title}} without template overhead
		elseif ('none' ~= args_t.display_title) then							-- any value but 'none'	use value in |display_title= for {{DISPLAYTITLE}} magic word
			infobox_ship_t.title = infobox_ship_t.title .. frame:preprocess (DISPLAYTITLE_make (args_t.display_title));
		end
	else																		-- |display_title= empty or omitted, use article title
		infobox_ship_t.title = infobox_ship_t.title .. frame:preprocess (DISPLAYTITLE_make (ship_name_format ({name=name, sclass=args_t.sclass})));
	end
end	


--[[--------------------------< I M A G E _ P A R A M S _ C H E C K >------------------------------------------

emit maint cat and warning message when:
	- |image_caption= and/or |image_alt= have a value but |image= does not have a value.
	- |image_size=300px
	- |image= has extended image syntax

]]

local function image_params_check (args_t)
	local image_warning_flag;													-- flag; true when ibox has |image_alt= or |image_caption= but no |image=
	local template_name = 'Infobox ship/image';

	local image_link = args_t.image and args_t.image:match ('^%b[]');			-- get first wikilink in |image= if any
	if image_link and (image_link:match ('%[%[%s*[Ff]ile:') or image_link:match ('%[%[%s*[Ii]mage:')) then	-- check for |image= with extended image syntax
		mw.addWarning (string.format (data.image_EIS_warning_fmt_str, data.warn_span_style, data.warn_code_style, template_name, template_name, data.warn_code_style));	-- extended image syntax found add warning
		image_warning_flag = true;
	end
	
	if args_t.image_size and args_t.image_size:match ('300') then			-- 300 is default size so this parameter/value pair superfluous
		mw.addWarning (string.format (data.image_size_warning_fmt_str, data.warn_span_style, data.warn_code_style, template_name, template_name, data.warn_code_style));	-- add warning
		image_warning_flag = true;
	end

	if not args_t.image and (args_t.image_caption or args_t.image_alt) then	-- caption or alt without image is meaningless
		mw.addWarning (string.format (data.image_missing_warning_fmt_str, data.warn_span_style, data.warn_code_style, template_name, template_name, data.warn_code_style, data.warn_code_style, data.warn_code_style, data.warn_code_style));	-- add warning
		image_warning_flag = true;
	end
	
	return image_warning_flag;
end


--[[--------------------------< I N F O B O X _ S H I P _ I M A G E >------------------------------------------

{{#invoke:Infobox ship|infobox_ship_image}} – for stand-alone mode

Returns a child infobox for Infobox ship/image unless |child=no for a stand-alone rendering

images in {{infobox ship}} default to 300px; upright is ignored

]]

local function infobox_ship_image (frame)
	local template_name = 'Infobox ship/image';
	local args_t = get_args (frame);

	local image_warning_flag = image_params_check (args_t) and data.cat_this_namespace;	-- and categorize any warnings

	local infobox_ship_t = {
		child = args_t.child or 'yes';											-- default to child
		image1 = require ('Module:InfoboxImage').InfoboxImage ({args = {'InfoboxImage', 'InfoboxImage', image=args_t.image, size=args_t.image_size, alt=args_t.image_alt, sizedefault = '300px'}});	-- upright='1' ignored
		caption1 = args_t.image_caption;
		}

	local has_unknown_params = unknown_params_check (frame:getParent().args, data.known_infobox_ship_image_params_t, template_name);	-- emit warning for all unknown parameters; even those that are empty

	return table.concat ({
		final_rendering (infobox_ship_t, frame, has_unknown_params, 'image'),	-- return final rendering of this infobox
		(has_unknown_params and data.categories_t.unknown) or '',
		(image_warning_flag and data.categories_t.image_syntax) or '',
		((not args_t.image) and (data.cat_this_namespace and data.categories_t.no_image)) or '',
		});
end


--[[--------------------------< I N F O B O X _ S H I P _ C A R E E R >----------------------------------------

{{#invoke:Infobox ship|infobox_ship_career}} – for stand-alone mode

Returns a child infobox for Infobox ship/career unless |child=no for a stand-alone rendering

]]

local function infobox_ship_career (frame)
	local template_name = 'Infobox ship/career';
	local args_t = get_args (frame);
	args_t.hide_header= args_t.hide_header and args_t.hide_header:lower();		-- set to lowercase if set

	local infobox_ship_t = {
		child = args_t.child or 'yes';											-- default to child
		headerclass = 'country';
		}

	if args_t.infobox_caption then												-- special case for |infobox_caption=
		if 'yes' == infobox_ship_t.child then									-- if not stand-alone
			add_warning (template_name, 'infobox_caption', args_t.infobox_caption);	--emit preview warning message
		else
			infobox_ship_t.title = args_t.infobox_caption;						-- translate |infobox_caption= to |title= used by Module:Infobox
			args_t.infobox_caption = nil;										-- unset to avoid preview error message; no longer needed
		end
	end
		
	local i = 1;

	if 'yes' ~= args_t.hide_header then											-- |hide_header=yes then no header
		if not ('title' == args_t.hide_header) then								-- |hide_header=title then no title bar
			local spoof_t = {
				child = 'yes',													-- default to child
				decat = 'yes',													-- spoof infobox does not have data; don't categorize in Category:Articles using infobox templates with no data rows
				header1 = 'History',
				headerclass = "history",
				}

			infobox_ship_t.data1 = frame:expandTemplate ({title='Infobox', args = spoof_t});	-- return a rendering of this spoof infobox
			i = i + 1;
		end

		if args_t.country and args_t.flag then
			infobox_ship_t['header' .. i] = infobox_ship_flag (args_t.flag) .. '<span style="padding-left:1em">' .. args_t.country .. '</span>';
		elseif args_t.country then
			infobox_ship_t['header' .. i] = args_t.country;
		elseif args_t.flag then
			infobox_ship_t['header' .. i] = infobox_ship_flag (args_t.flag);
		end
	end

	if infobox_ship_t['header' .. i] then
		i = i + 1;
	end
	
	local error_flag;
	error_flag = synonym_check (args_t, 'ship_stricken', 'ship_struck', error_flag);	-- error if both synonymous parameters set
	synonym_check (args_t, 'ship_honours', 'ship_honors', error_flag);

	line_items (args_t, data.infobox_career_params_t, infobox_ship_t, i, frame);		-- go do all of the other infobox parameters
	local has_unknown_params = unknown_params_check (frame:getParent().args, data.known_infobox_ship_career_params_t, 'Infobox ship/career');	-- emit warning for all unknown parameters; even those that are empty

	return table.concat ({
		final_rendering (infobox_ship_t, frame, has_unknown_params, 'career'),			-- return final rendering of this infobox
		(has_unknown_params and data.categories_t.unknown) or ''
		});
end


--[[--------------------------< I N F O B O X _ S H I P _ C H A R A C T E R I S T I C S >----------------------

{{#invoke:Infobox ship|infobox_ship_characteristics}} – for stand-alone mode

Returns a child infobox for Infobox ship/characteristics unless |child=no for a stand-alone rendering

]]

local function infobox_ship_characteristics (frame)
	local template_name = 'Infobox ship/characteristics';
	local args_t = get_args (frame);
	args_t.hide_header= args_t.hide_header and args_t.hide_header:lower();		-- set to lowercase if set

	local infobox_ship_t = {
		child = args_t.child or 'yes',											-- default to child
		headerclass = 'general'
		}
	
	if args_t.infobox_caption then												-- special case for |infobox_caption=
		if 'yes' == infobox_ship_t.child then									-- if not stand-alone
			add_warning (template_name, 'infobox_caption', args_t.infobox_caption);	--emit preview warning message
		else
			infobox_ship_t.title = args_t.infobox_caption;						-- translate |infobox_caption= to |title= used by Module:Infobox
			args_t.infobox_caption = nil;										-- unset to avoid preview error message; no longer needed
		end
	end
		
	local i = 1;

	if 'yes' ~= args_t.hide_header then											-- |hide_header=yes then no header
		local header = 'General characteristics';								-- the default header
		if args_t.header_caption then
			header = header .. ' ' .. args_t.header_caption;					-- concatenate |header_caption= onto default header
		end
		infobox_ship_t['header' .. i] = header;									-- add the header
		i = i + 1;																-- bump the enumerator
	end

	local error_flag;
	error_flag = synonym_check (args_t, 'ship_armour', 'ship_armor', error_flag);	-- error if both synonymous parameters set
	synonym_check (args_t, 'ship_draught', 'ship_draft', error_flag);				-- when both set modify with error message and category

	line_items (args_t, data.infobox_characteristics_params_t, infobox_ship_t, i, frame);	-- go do all of the other infobox parameters
	local has_unknown_params = unknown_params_check (frame:getParent().args, data.known_infobox_ship_characteristics_params_t, template_name);	-- emit warning for all unknown parameters; even those that are empty

	return table.concat ({
		final_rendering (infobox_ship_t, frame, has_unknown_params, 'characteristics'),			-- return final rendering of this infobox
		(has_unknown_params and data.categories_t.unknown) or ''
		});
end


--[[--------------------------< I N F O B O X _ S H I P _ C L A S S _ O V E R V I E W >------------------------

{{#invoke:Infobox ship|infobox_ship_class_overview}} – for stand-alone mode

Returns a child infobox for Infobox ship/class overview unless |child=no for a stand-alone rendering

]]

local function infobox_ship_class_overview (frame)
	local template_name = 'Infobox ship/class overview';
	local args_t = get_args (frame);
	args_t.hide_header= args_t.hide_header and args_t.hide_header:lower();		-- set to lowercase if set

	local infobox_ship_t = {
		child = args_t.child or 'yes',											-- default to child
		headerclass = 'general'
		}
	
	if args_t.infobox_caption then												-- special case for |infobox_caption=
		if 'yes' == infobox_ship_t.child then									-- if not stand-alone
			add_warning (template_name, 'infobox_caption', args_t.infobox_caption);	--emit preview warning message
		else
			infobox_ship_t.title = args_t.infobox_caption;						-- translate |infobox_caption= to |title= used by Module:Infobox
			args_t.infobox_caption = nil;										-- unset to avoid preview error message; no longer needed
		end
	end

	local i = 1;

	if 'yes' ~= args_t.hide_header then											-- |hide_header=yes then no header
		infobox_ship_t['header' .. i] = 'Class overview';						-- add the header
		i = i + 1;																-- bump the enumerator
	end

	synonym_check (args_t, 'total_ships_cancelled', 'total_ships_canceled', nil);	-- error if both synonymous parameters set

	line_items (args_t, data.infobox_class_overview_params_t, infobox_ship_t, i, frame);	-- go do all of the other infobox parameters
	local has_unknown_params = unknown_params_check (frame:getParent().args, data.known_infobox_ship_class_overview_params_t, template_name);	-- emit warning for all unknown parameters; even those that are empty

	return table.concat ({
		final_rendering (infobox_ship_t, frame, has_unknown_params, 'class overview'),			-- return final rendering of this infobox
		(has_unknown_params and data.categories_t.unknown) or ''
		});
end


--[[--------------------------< I N F O B O X _ S H I P _ S E R V I C E _ R E C O R D >------------------------

{{#invoke:Infobox ship|infobox_ship_service_record}} – for stand-alone mode

Returns a child infobox for Infobox ship/service record unless |child=no for a stand-alone rendering.  This
function implements the ship-only portion of {{Infobox service record}}; does not know about |is_ship= and
|is_multi= parameters.  Adds support for |hide_header= parameter

]]

local function infobox_ship_service_record (frame)
	local template_name = 'Infobox ship/service record';
	local args_t = get_args (frame);
	args_t.hide_header= args_t.hide_header and args_t.hide_header:lower();		-- set to lowercase if set

	local infobox_ship_t = {
		child = args_t.child or 'yes',											-- default to child
		headerclass = 'general'
		}
	
	if args_t.infobox_caption then												-- special case for |infobox_caption=
		if 'yes' == infobox_ship_t.child then									-- if not stand-alone
			add_warning (template_name, 'infobox_caption', args_t.infobox_caption);	--emit preview warning message
		else
			infobox_ship_t.title = args_t.infobox_caption;						-- translate |infobox_caption= to |title= used by Module:Infobox
			args_t.infobox_caption = nil;										-- unset to avoid preview error message; no longer needed
		end
	end

	local i = 1;

	if 'yes' ~= args_t.hide_header then											-- |hide_header=yes then no header
		infobox_ship_t['header' .. i] = args_t.header_caption or args_t.label or 'Service record';		-- add the header; |label= is deprecated
		i = i + 1;																-- bump the enumerator
	end

	line_items (args_t, data.infobox_ship_service_record_params_t, infobox_ship_t, i, frame);	-- go do all of the other infobox parameters
	local has_unknown_params = unknown_params_check (frame:getParent().args, data.known_infobox_ship_service_record_params_t, template_name);	-- emit warning for all unknown parameters; even those that are empty

	return table.concat ({
		final_rendering (infobox_ship_t, frame, has_unknown_params, 'service record'),			-- return final rendering of this infobox
		(has_unknown_params and data.categories_t.unknown) or ''
		});
end


--[[--------------------------< I N F O B O X _ S H I P >------------------------------------------------------

{{#invoke:Infobox ship|infobox_ship}}

To discover when two child infoboxen are concatenated without a proper |sectionn= parameter between them, this
function looks for and counts the special secret |label100=__1B0X_5H1P__ and |data100=<template name>.  Each child
appends the special secret parameters at final_rendering().  This function counts and deletes those special secret
parameters.  If the count is:
	0 – ok; parameter does not include a child infobox rendering
	1 – ok; parameter holds only one child infobox
	2+ – not ok

]]

local function infobox_ship (frame)
	local args_t = get_args (frame);

	local infobox_ship_t = {};													-- table that holds infobox parameters

	infobox_ship_begin (args_t, infobox_ship_t, frame);							-- fetch parameters that once belonged to obsolete {{infobox ship begin}}

	for k, v in pairs (args_t) do												-- copy infobox parameters from the frame into our local table
		if 'string' == type (k) then											-- must be string; positional parameters not supported
			local enum = k:match ('section(%d+)');								-- <enum> gets a value when this parameter is |section<n>= 
			if enum then
				infobox_ship_t['data'..enum] = v;								-- translate |section<n> = to |data<n>=
			else
				infobox_ship_t[k] = v;											-- assume that parameter name is one known to Module:Infobox
			end
		end
	end

	for k, v in pairs (infobox_ship_t) do										-- look for malformed input (concatenated child infoboxen)
		if k:find ('data', 1, true) then										-- only look in values assigned to |datan= (née |sectionn=)
			local name, count = v:gsub ('<tr><th scope=\"row\" class=\"infobox%-label\">__1B0X_5H1P__</th><td class=\"infobox%-data\">[%l ]+</td></tr>', '');	-- count special secret parameters 

			if 1 < count then													-- more than one when parameter has concatenated child infoboxen
				infobox_ship_t[k] = table.concat ({
					string.format (data.error_messages_t.missing_section, k:match ('%d')),		-- emit an error message; replaces the concatenated subtemplate
					(data.cat_this_namespace and data.categories_t.missing_section) or '',		-- and also categorize
					});
			else
				infobox_ship_t[k] = name;										-- delete special secret |label100=__1B0X_5H1P__ and |data100=<template name>
			end
		end
	end

	local has_unknown_params = unknown_params_check (frame:getParent().args, data.known_infobox_ship_params_t, 'Infobox ship');	-- emit warning for all unknown parameters; even those that are empty

	return table.concat ({
			frame:extensionTag ('templatestyles', '', {src='Module:Infobox ship/styles.css'}),	-- apply template styles
			infobox (infobox_ship_t),											-- render the infobox
			(has_unknown_params and data.categories_t.unknown) or ''			-- add category for any unknown parameters
		});
end

		
--[[--------------------------< E X P O R T S >----------------------------------------------------------------
]]

return {
	infobox_ship = infobox_ship,
	infobox_ship_image = infobox_ship_image,
	infobox_ship_career = infobox_ship_career,
	infobox_ship_characteristics = infobox_ship_characteristics,
	infobox_ship_class_overview = infobox_ship_class_overview,
	infobox_ship_service_record = infobox_ship_service_record,
	}