Modulː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 = 'ib-ship';									-- use basic infobox css for stand-alone mode
		infobox_ship_t.templatestyles = data.templatestyles -- apply templatestyles
		return table.concat ({			
				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  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 ('', 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 ()

TODO: can we make |infobox_caption=yes render the caption in the same format as the displayed article title?  Do
this instead of forcing editors to write and format the caption manually when the article title doesn't have ship
prefix or disambiguation.

]]

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

	infobox_ship_t['bodyclass'] = 'ib-ship';

	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  template
			infobox_ship_t.title = infobox_ship_t.title .. require ('Module:italic title')._main ({});	--  without template overhead
		elseif ('none' ~= args_t.display_title) then							-- any value but 'none'	use value in |display_title= for  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 >------------------------------------------

 – for stand-alone mode

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

images in  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;	-- categorize any image warnings
	local non_std_img_size_flag = args_t.image_size and not args_t.image_size:match ('300') and data.cat_this_namespace;	-- not a warning; categorize non-standar image size use

	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 ,
		(non_std_img_size_flag and data.categories_t.non_std_img_size ) or ,
		});
end

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

 – 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 = 'ib-ship-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 = "ib-ship-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) .. '' .. args_t.country .. '';
		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 >----------------------

 – 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 = 'ib-ship-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 >------------------------

 – 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 = 'ib-ship-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 >------------------------

 – 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 ; 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 = 'ib-ship-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 >------------------------------------------------------

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 

	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 ('__1B0X_5H1P__[%l ]+', );	-- 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

	infobox_ship_t.templatestyles = data.templatestyles -- apply template styles
	return table.concat ({
			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,
	}