local f = {};

--[[--------------------------< I S _ S E T >------------------------------------------------------------------

Whether variable is set or not.  A varable is set when it is not nil and not empty.

]]

local function is_set( var )
	return not (var == nil or var == '');
end

--[[--------------------------< M A K E _ N A M E >------------------------------------------------------------

Assembles last, first, link, or mask into a displayable contributor name.

]]

local function make_name (last, first, link, mask)
	local name = last;
	
	if is_set (first) then
		name = name .. ', ' .. first;											-- concatenate first onto last
	end
	
	if is_set (link) then
		name = '[[' .. link .. '|' .. name .. ']]';								-- form a wikilink around the name
	end
	
	if is_set (mask) then														-- mask this author
		mask = tonumber (mask);													-- because the value provided might not be a number
		if is_set (mask) then
			name = string.rep ('—', mask)										-- make a string that number length of mdashes
		end
	end
	
	return name;
end


--[[--------------------------< C O R E >----------------------------------------------------------------------

Assembles the various parts provided by the template into a properly formatted bridging citation.  Adds punctuation
and text; encloses the whole within a span with id and class attributes.

This creates a CITEREF anchor from |last1= through |last4= and |year=.  It also creates a CITEREF link from |in1= through
|in4= and |year=.  It is presumed that the dates of contributions are the same as the date of the enclosing work.

Even though not displayed, a year parameter is still required for the CITEREF anchor

]]

local function core( args )
	local span_open_tag;			-- holds CITEREF and css
	local contributors = '';		-- chapter or contribution authors
	local source = '';				-- editor/author date list that forms a CITEREF link to a full citation; mimics harvnb output except year in parentheses
	local in_text = ' In ';			-- 
	local result;					-- the assemby of the above output

-- form the CITEREF anchor
	if is_set (args.id) then
		span_open_tag = '<span id="' .. args.id .. '" class="citation">';		-- for use when contributor name is same as source name
	else
		span_open_tag = '<span id="CITEREF' .. table.concat (args.citeref) .. args.year .. '" class="citation">';
	end
 
--[[
form the contributors display list:
	if |name-list-format=harv, display is similar to {{sfn}} and {{harv}}, 1 to 4 last names;
	if |display-authors= is empty or omitted, display is similar to cs1|2: display all names in last, first order 
	if |display-authors=etal then displays all author names in last, first order and append et al.
	if value assigned to |display-authors= is less than the number of author last names, displays the specified number of author names in last, first order followed by et al.
]]
	if 'harv' ~= args.name_list_format then										-- default cs1|2 style contributor list
		local i = 1;
		local count;
		local etal = false;														-- when |display-authors= is same as number of authors in contributor list
		
		if is_set (args.display_authors) then
			if 'etal' == args.display_authors:lower():gsub("[ '%.]", '') then	-- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
				count = #args.last;												-- display all authors and ...
				etal = true;													-- ... append 'et al.'
			else
				count = tonumber (args.display_authors) or 0;					-- 0 if can't be converted to a number
				if 0 >= count then
					args.err_msg = args.err_msg .. ' invalid |display-authors=';	-- if zero, then emit error message
				end
			end
			if count > #args.last then
				count = #args.last;												-- when |display-authors= is more than the number of authors, use the number of authors
			end
			if count < #args.last then											-- when |display-authors= is less than the number of authors
				etal = true;													-- append 'et al.'
			end
		else
			count = #args.last;													-- set count to display all of the authors
		end
		
		while i <= count do
			if is_set (contributors) then
				contributors = contributors .. '; ' .. make_name (args.last[i], args.first[i], args.link[i], args.mask[i]);			-- the rest of the contributors
			else
				contributors = make_name (args.last[i], args.first[i], args.link[i], args.mask[i]);			-- first contributor's name
			end
			i = i+1;															-- bump the index
		end
		if true == etal then
			contributors = contributors .. ' et al.';							-- append et al.
		elseif 'yes' == args.last_author_amp then
			contributors = contributors:gsub('; ([^;]+)$', ' & %1')				-- replace last separator with ' & '
		end
	else																		-- do default harv- or sfn-style contributor display
		if 4 <= #args.last then													-- four or more contributors (first followed by et al.)
			contributors = args.last[1] .. ' et al.';
		elseif 3 == #args.last then												-- three (display them all)
			contributors = args.last[1] .. ', ' .. args.last[2] .. ' &amp; ' .. args.last[3];
		elseif 2 == #args.last then												-- two (first & second)
			contributors = args.last[1] .. ' &amp; ' .. args.last[2];
		elseif 1 == #args.last then												-- just one (first)
			contributors = args.last[1];
		else
			args.err_msg = args.err_msg .. ' no authors in contributor list.';	-- this code used to find holes in the list; no more
		end
	end

--form the source author-date list
	if is_set (args.in4) and is_set (args.in3) and is_set (args.in2) and is_set (args.in1) then
		source = args.in1 .. ' et al.';
	elseif not is_set (args.in4) and is_set (args.in3) and is_set (args.in2) and is_set (args.in1) then
		source = args.in1 .. ', ' .. args.in2 .. ' &amp; ' .. args.in3;
	elseif not is_set (args.in4) and not is_set (args.in3) and is_set (args.in2) and is_set (args.in1) then
		source = args.in1 .. ' &amp; ' .. args.in2;
	elseif not is_set (args.in4) and not is_set (args.in3) and not is_set (args.in2) and is_set (args.in1) then
		source = args.in1;
	else
		args.err_msg = args.err_msg .. ' author missing from source list.'
	end

 	if args.year:match('^[1-9]%d%d%d?%l?$') or args.year:match('^n%.d%.%l?$') or args.year:match('^nd%l?$') then	-- 3 or 4 digits, n.d., or nd and optional disambiguator
		source = source .. ' (' .. args.year .. ')';
	else
		args.err_msg = args.err_msg .. ' invalid or missing year.';				-- error message if year not provided or is imalformed
	end	

--assemble CITEREF wikilink
	source = "[[#CITEREF" .. mw.uri.anchorEncode(table.concat ({args.in1, args.in2, args.in3, args.in4, args.year})) .. "|" .. source .. "]]";

--combine contribution with url to make external link
	if args.url ~= '' then
		args.contribution = '[' .. args.url .. ' ' .. args.contribution .. ']';	-- format external link
	end
	if args.sepc ~= contributors:sub(-1) and args.sepc .. ']]' ~= contributors:sub(-3) then
		contributors = contributors .. args.sepc;								-- add separator if not same as last character in name list (|first=John S. or et al.)
	end

-- pages and other insource location
	if args.p ~= '' then
		args.p = args.page_sep .. args.p;
	elseif args.pp ~= '' then
		args.p = args.pages_sep .. args.pp;										-- args.p not set so use it to hold common insource location info
	end      
 
	if args.loc ~= '' then
		args.p = args.p .. ', ' .. args.loc;									-- add arg.loc to args.p
	end

--wrap error messages in span and add help link
	if is_set (args.err_msg) then
		args.err_msg = '<span style="font-size:100%" class="error"> harvc:' .. args.err_msg .. ' ([[Template:Harvc|help]])</span>';
	end

	if ',' == args.sepc then
		in_text = in_text:lower();												-- CS2 style use lower case
	end

-- and put it all together
	result = span_open_tag .. contributors .. ' "' .. args.contribution .. '"' .. args.sepc .. in_text .. source .. args.p .. args.ps .. '</span>' .. args.err_msg;

	return result;
end


--[[--------------------------< F . H A R V C >----------------------------------------------------------------

Entry point from {{harvc}} template.  Fetches parent frame parameters, does a bit of simple error checking

]]
function f.harvc (frame)
	local args = {
		err_msg = '',
		page_sep = ", p.&nbsp;",
		pages_sep = ", pp.&nbsp;",
		sepc = '.',
		ps = '.',
		last = {},
		first = {},
		link = {},
		mask = {},
		citeref = {}
		}

	local pframe = frame:getParent();
 
	args.contribution =  pframe.args.c or										-- chapter or contribution
				pframe.args.chapter or
				pframe.args.contribution or '';

	args.id = pframe.args.id or '';

	args.in1 = pframe.args['in'] or pframe.args.in1 or '';						-- source editor surnames; 'in' is a Lua reserved keyword
	args.in2 = pframe.args.in2 or '';
	args.in3 = pframe.args.in3 or '';
	args.in4 = pframe.args.in4 or '';

	args.display_authors = pframe.args['display-authors'];						-- the number of contributor names to display; cs1/2 format includes first names
	args.name_list_format = pframe.args['name-list-format'];					-- when set to 'harv' display contributor list in sfn or harv style
	args.last_author_amp = pframe.args['last-author-amp'] or					-- yes only; |last-author-amp=no does not work (though it does in CS1|2)
				pframe.args['lastauthoramp'] or '';
	args.last_author_amp:lower();												-- make it case agnostic
	
	if is_set (pframe.args['last1']) or is_set (pframe.args['last']) then		-- must have at least this to continue
		args.last[1] = pframe.args.last or pframe.args.last1;					-- get first contributor's last name
		args.citeref[1] = args.last[1];											-- add it to the citeref
		args.first[1] = pframe.args.first or pframe.args.first1;				-- get first contributor's first name
		args.link[1] = pframe.args['author-link'] or pframe.args['author-link1'];	-- get first contributor's article link
		args.mask[1] = pframe.args['author-mask'] or pframe.args['author-mask1'];	-- get first contributor's article link
	
		local i = 2;															-- index for the rest of the names
		while is_set (pframe.args['last'..i]) do								-- loop through pfram.args and get the rest of the names
			args.last[i] = pframe.args['last'..i];								-- last names
			args.first[i] = pframe.args['first'..i];							-- first names
			args.link[i] = pframe.args['author-link'..i];						-- first names
			args.mask[i] = pframe.args['author-mask'..i];						-- first names
			if 5 > i then
				args.citeref[i] = args.last[i];									-- collect first four last names for CITEREF anchor
			end
			i = i + 1															-- bump the index
		end
	end

	args.p = pframe.args.p or '';												-- source page number(s) or location
	args.pp = pframe.args.pp or '';
	args.loc = pframe.args.loc or '';

	if 'cs2' == pframe.args.mode then
		args.ps = '';															-- set postscript character to empty string, cs2 mode
		args.sepc = ',';														-- set seperator character to comma, cs2 mode
	end
	do																			-- to limit scope of local temp
		local temp = pframe.args.ps or pframe.args.postscript;
		
		if is_set (temp) then
			if 'none' == temp:lower() then										-- if |ps=none or |postscript=none then
				args.ps = '';													-- no postscript
			else
				args.ps = temp;													-- override default postscript
			end
		end
	end																			-- end of scope limit

	args.url = pframe.args.url or												-- url for chapter or contribution
			pframe.args['chapter-url'] or
			pframe.args['contribution-url'] or '';
	
	args.year = pframe.args.year or '';											-- required

	if not is_set (args.contribution) then
		args.err_msg = args.err_msg .. ' required contribution is missing.';	-- error message if source not provided
		args.contribution = args.url;											-- if set it will give us linkable text
	end
	
	if args.last[1] == args.in1 and
		args.last[2] == args.in2 and
		args.last[3] == args.in3 and
		args.last[4] == args.in4 and
		not is_set (args.id) then
			args.err_msg = args.err_msg .. ' required &#124;id= parameter missing.';		-- error message if contributor and source are the same
	end

	return core (args);
end

return f;