marvel

-- module for Marvel Database:Character Template
local p = {}
local getArgs = require('Dev:Arguments').getArgs
local h = require("Module:HF")
local design = require('Module:Design')
local units  = require('Module:Units')	
local list_of_power_grids = mw.loadData('Module:Power Grid/List')

function p.main(frame)
	local args = getArgs(frame)
	local pagename = mw.title.getCurrentTitle().text
	local page_type = 'Character'
	local value
	local categories = {}
	local output_categories = {}
	local output = {}

	output_categories = p.lua_get_outdated_fields(args, pagename)
-- sections
	-- adds 'Quote', 'Overview' section, 'TOC' and 'History' section
	value, categories = design.add_quote_overview_toc_history(args, page_type)
	output_categories = h.join_tables(output_categories, categories)
	output = h.join_tables(output, value)

	table.insert( output, design.add_section('Personality', args.Personality, 2) )

	if not h.isempty(args.Powers) 
	or not h.isempty(args.Abilities) 
	or not h.isempty(args.Weaknesses) 
	or not h.isempty(args.AdditionalAttributes) 
	or list_of_power_grids[pagename] ~= nil
		then
			table.insert( output, design.add_header('Attributes', 2) )
			if list_of_power_grids[pagename] ~= nil
				then 
					value, categories = require('Module:Power Grid').main(pagename)
					output_categories = h.join_tables(output_categories, categories)
					table.insert(output, value)
			end
			table.insert( output, design.add_section('Powers', args.Powers, 3) )
			table.insert( output, design.add_section('Abilities', args.Abilities, 3) )
			table.insert( output, design.add_section('Weaknesses', args.Weaknesses, 3) )
			table.insert( output, design.add_section('Additional Attributes', args.AdditionalAttributes, 3) )
	end
	
	if not h.isempty(args.Equipment) or not h.isempty(args.Weapons)or not h.isempty(args.Transportation) 
		then
			table.insert( output, design.add_header('Paraphernalia', 2) )
			table.insert( output, design.add_section('Equipment', args.Equipment, 3) )
			table.insert( output, design.add_section('Weapons', args.Weapons, 3) )
			table.insert( output, design.add_section('Transportation', args.Transportation, 3) )
	end

	-- adds 'Notes', 'Trivia', 'See Also', 'Recommended Reading' and 'Links and References' sections
	---- links to standard sub-pages/categories - "Appearances", "Minor Appearances", Mentions", "Images", "Quotes" and "Gallery" 
	value, categories = design.add_notes_trivia_see_also_recommended_links_references(args, page_type, pagename)
	output_categories = h.join_tables(output_categories, categories)
	output = h.join_tables(output, value)

--
	output_categories = h.add_categories(output_categories)
	output = table.concat(output)
	
	return frame:preprocess(output)..output_categories
end

--------------------------------------------------------------------------------------------------
function p.lua_get_outdated_fields(args, pagename)
	local relatives = args.Relatives
	local output_categories = {}
	
	if  not h.isempty(args.Custom)
		or not h.isempty(args.CustomLabel)
		or not h.isempty(args.CustomSection1)
		or not h.isempty(args.CustomText1)
		or not h.isempty(args.CustomSection2)
		or not h.isempty(args.CustomText2)
		or not h.isempty(args.NotesHeader)
		or not h.isempty(args.TriviaHeader)
		or not h.isempty(args.LinksHeader)
		or not h.isempty(args.HistoryHeader)
		or not h.isempty(args.RecommendedHeader)
		or not h.isempty(args.PersonalityHeader)
		or not h.isempty(args.Height2)
		or not h.isempty(args.Weight2)
		or not h.isempty(args.Eyes2)
		or not h.isempty(args.Hair2)
		or not h.isempty(args.Gender2)
		or not h.isempty(args.Citizenship2) -- temporarily, to correctly put all citizenships into "Citizenship" field
		or not h.isempty(args.UnusualSkinColour)
		or not h.isempty(args.UnusualSkinColour2)
		or not h.isempty(args.UnusualSkinColor)
		or not h.isempty(args.UnusualSkinColor2)
		or not h.isempty(args.Skin2)
		or not h.isempty(args.MaritalStatus2) 
		or not h.isempty(args.PowersAbilitiesHeader)
		or not h.isempty(args.ParaphernaliaHeader)
		or not h.isempty(args.DiscoverAndDiscussHeader)
		or not h.isempty(args.OtherMedia)
		or not h.isempty(args.UniverseRef)
		or not h.isempty(args.Last)
		or not h.isempty(args.Strength)
			then table.insert(output_categories, 'Outdated Fields/Character')
	end

	if not h.isempty(relatives)
		then 
			relatives = string.lower(relatives)
			if string.find(relatives, '-grandfather', 1, true) or string.find(relatives, '-grandmother', 1, true) or string.find(relatives, '-grandparent', 1, true) or string.find(relatives, 'ancestor')
				then table.insert(output_categories, 'Relatives Correction Needed/Ancestors')
				elseif string.find(relatives, 'grandfather') or string.find(relatives, 'grandmother') or string.find(relatives, 'grandparent')
					then table.insert(output_categories, 'Relatives Correction Needed/Grandparents')
				elseif string.find(relatives, 'father') or string.find(relatives, 'mother')
					then table.insert(output_categories, 'Relatives Correction Needed/Parents')
			end
			if string.find(relatives, 'grandson') or string.find(relatives, 'granddaughter') or string.find(relatives, 'grandchild') or string.find(relatives, 'descendant')
				then table.insert(output_categories, 'Relatives Correction Needed/Descendants')
				elseif string.find(relatives, ' son') or string.find(relatives, '(son', 1, true) or string.find(relatives, 'daughter') or string.find(relatives, 'child')
					then table.insert(output_categories, 'Relatives Correction Needed/Children')
			end
			if string.find(relatives, 'brother') or string.find(relatives, 'sister') or string.find(relatives, 'sibling')
				then table.insert(output_categories, 'Relatives Correction Needed/Siblings')
			end
			if string.find(relatives, 'husband') or string.find(relatives, 'wife') or string.find(relatives, 'spouse')
				then table.insert(output_categories, 'Relatives Correction Needed/Spouses')
			end
			if string.find(relatives, '^host') ~= nil or string.find(relatives, ' host') ~= nil or string.find(relatives, '%(host') ~= nil or ( string.find(relatives, 'symbiote') ~= nil and string.find(pagename, 'Symbiote') == nil )
				then table.insert(output_categories, 'Relatives Correction Needed/Hosts')
			end
			if string.find(relatives, 'clone') ~= nil or string.find(relatives, 'genetic template') ~= nil or string.find(relatives, 'genetic donor') ~= nil
				or (not h.isempty(args.Parents) and string.find(args.Parents, 'genetic template') ~= nil)
				or (not h.isempty(args.Siblings) and string.find(args.Siblings, 'clone') ~= nil)
				then table.insert(output_categories, 'Relatives Correction Needed/Clones')
			end
	end
	return output_categories	
end


--------------------------------------------------------------------------------------------------
function p.get_current_alias(frame)
	local args = getArgs(frame)
	local ref = args.CurrentAliasRef or ''
	local output = args.CurrentAlias
	
	if not h.is_link(output) and h.exists(output)
		then output = h.Link(output)
	end

	return output..ref
end


--------------------------------------------------------------------------------------------------
function p.get_aliases(frame)
	local args = getArgs (frame)
	local list = {args.Codenames, args.EditorialNames, args.Titles, args.Nicknames, args.Impersonations, args.Aliases}
	local labels = {'Codenames', 'Editorial Names', 'Titles and Ranks', 'Nicknames', 'Impersonations', 'Other Aliases'}
	local value
	local i
	local output = {}

	for i = 1, #labels do
		if not h.isempty(list[i])
			then
				if labels[i] == 'Other Aliases' and table.concat(output) == '' 
					then 
						table.insert(output, list[i])
					else
						if design.count_lines(list[i]) > design.COLLAPSIBLE_MAX_LINES
							then value = design.show_hide({header = labels[i]..':', body = '\n'..list[i], collapsed = 'true', expandtext = '+', collapsetext = '-' })
							else value = design.span(labels[i]..':').bold..'\n'..tostring( mw.html.create('div'):wikitext('\n'..list[i]) )
						end
						table.insert(output, value)
				end
		end
	end
	output = mw.text.listToText(output, '\n', '\n') 

	return design.add_infobox_row_collapsible({output, no_collapse = 1})
end


--------------------------------------------------------------------------------------------------
function p.get_relatives(frame)
	local args = getArgs (frame)
	local list = {args.Ancestors, args.Grandparents, args.Parents, args.Clones, args.Siblings, args.Spouses, args.Children, args.Descendants, args.Relatives, args.InLaw}
	local labels = {'Ancestors', 'Grandparents', 'Parents', 'Clones and Donors', 'Siblings', 'Spouses', 'Children', 'Descendants', 'Other Relatives', 'Family-in-Law'}
	local value
	local i
	local output = {}

	for i = 1, #labels do
		if not h.isempty(list[i])
			then
				if labels[i] == 'Other Relatives' and table.concat(output) == '' 
					then 
						table.insert(output, list[i])
					else
						if design.count_lines(list[i]) > design.COLLAPSIBLE_MAX_LINES
							then value = design.show_hide({header = labels[i]..':', body = '\n'..list[i], collapsed = 'true', expandtext = '+', collapsetext = '-' })
							else value = design.span(labels[i]..':').bold..'\n'..tostring( mw.html.create('div'):wikitext('\n'..list[i]) )
						end
						table.insert(output, value)
				end
		end
	end
	output = mw.text.listToText(output, '\n', '\n') 
	
	return design.add_infobox_row_collapsible({output, no_collapse = 1})
end


--------------------------------------------------------------------------------------------------
function p.get_living_status(frame)
	local args = getArgs (frame)
	local cause_of_death = p.get_cause_of_death(frame)
	local living_status = args.Status
	local output_categories = {}
	local output = ''
	
	if living_status == 'Presumed Alive'
		then
			table.insert(output_categories, 'Presumed Alive Characters')
			output = h.LinkToCategory('Presumed Alive Characters', 'Presumed Alive')
	elseif living_status == 'Presumed Deceased'
		then
			table.insert(output_categories, 'Presumed Deceased Characters')
			output = h.LinkToCategory('Presumed Deceased Characters', 'Presumed Deceased')
	elseif living_status == 'Ghost'
		then
			table.insert(output_categories, 'Ghosts')
			output = h.LinkToCategory('Ghosts', 'Ghost')
	elseif living_status == 'Unknown'
		then
			table.insert(output_categories, 'Characters With Unknown Living Status')
			output = h.LinkToCategory('Characters With Unknown Living Status', 'Unknown')
	elseif living_status == 'Deceased' or living_status  == 'Dead' or not h.isempty(args.Death)
		then
			table.insert(output_categories, 'Deceased Characters')
			output = h.LinkToCategory('Deceased Characters', 'Deceased')
			if h.isempty(cause_of_death)
				then table.insert(output_categories, 'Cause of Death Needed')
			end
			if not h.isempty(living_status) and living_status ~= 'Deceased' and living_status ~= 'Dead'
				then output = output..' '..living_status
			end
	else
		table.insert(output_categories, 'Living Characters')
		output = h.LinkToCategory('Living Characters', 'Alive')
		if not h.isempty(cause_of_death)
			then 
				output = output..'; '..h.LinkToCategory('Formerly Deceased', 'formerly deceased')
				table.insert(output_categories, 'Formerly Deceased')
		end
		if not h.isempty(living_status)
			then output = output..' '..living_status
		end
	end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_cause_of_death(frame)
	local args = getArgs (frame)
	local killed = args.KilledBy
	local casualty = args.CasualtyOf
	local suicide = args.Suicide
	local sacrifice = args.Sacrifice
	local cause = args.CauseOfDeath
	local link = ''
	local i
	local output_categories = {}
	local output = ''
	
	if not h.isempty(cause)
		then 
			if #cause > 500
				then output = '<br>\n'..cause
				else output = cause
			end
			output = design.add_infobox_row_collapsible({output})
	end
	
	if not h.isempty(killed)
		then
			killed = h.explode(";", killed)
			for i = 1,#killed do
				link = h.break_link(h.trim(killed[i]), 1)
				table.insert(output_categories, 'Killed by '..link)
			end
	end
	
	if not h.isempty(casualty)
		then
			casualty = h.explode(";", casualty)
			for i = 1,#casualty do
				link = h.break_link(h.trim(casualty[i]), 1)
				table.insert(output_categories, link..' casualties')
			end
	end
	
	if not h.isempty(suicide)
		then table.insert(output_categories, 'Suicide')
	end
	
	if not h.isempty(sacrifice)
		then table.insert(output_categories, 'Self-sacrifice')
	end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_marital_status(frame)
	local function get_value_and_remainder(value)
		local i
		local remainder = ''

		i = string.find(value, ' ')
		if i ~= nil
			then 
				remainder = ' '..string.sub(value, i+1, #value)
				value = string.sub(value, 1, i-1)
		end

		return value, remainder
	end
	local args = getArgs (frame)
	local value = args.MaritalStatus
	local value2 = args.MaritalStatus2
	local list = {'Married', 'Divorced', 'Separated', 'Single', 'Widowed'}
	local l = {}
	local output_categories = {}
	local output = {}
	
	if not h.isempty(value)
		then
			output = {}
			l = h.explode(';', value)
			for i = 1, #l do
				value, remainder = get_value_and_remainder(h.trim(l[i]))
				if h.in_list(list, value)
					then 
						table.insert(output, h.LinkToCategory(value..' Characters', value)..remainder )
						table.insert(output_categories, value..' Characters')
					else 
						table.insert(output, h.LinkToCategory('Marital Status Needing Correction', value) )
				end
			end
			output = mw.text.listToText(output, ',<br>', ',<br>')
	end

	--if not h.isempty(value2)
	--	then table.insert(output, ' '..value2)
	--end
	
	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_occupation(frame)
	local args = getArgs (frame)
	local value = args.Occupation
	local occupations = require('Module:CharacterInfoboxOccupation')
	local output_categories = {}
	local output = ''
	
	if not h.isempty(value)
		then
			for key, v in pairs(occupations) do
				if string.find( string.lower(value), key ) ~= nil 
					then
						if h.in_list({"dancer", "designer"}, key) == false or (key == "dancer" and string.find( string.lower(value), "ballet dancer" ) == nil and string.find( string.lower(value), "exotic dancer" ) == nil) or (key == "designer" and string.find( string.lower(value), "fashion designer" ) == nil and string.find( string.lower(value), "weapon designer" ) == nil and string.find( string.lower(value), "weapons designer" ) == nil and string.find( string.lower(value), "weapon system designer" ) == nil and string.find( string.lower(value), "weapons systems designer" ) == nil)
							then
								for i, category in ipairs(v) do
									table.insert(output_categories, category)
								end
						end
				end
			end
	end

	return design.add_infobox_row_collapsible({value})..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_citizenship(frame)
	local data = require('Module:Nation and Citizenship')
	local args = getArgs (frame)
	local citizenship = args.Citizenship
	local info
	local value
	local valueLower
	local formerly
	local annulled
	local list = {}
	local i
	local output_categories = {}
	local output = {}
	
	if not h.isempty(citizenship)
		then 
			list = h.explode(';', citizenship)
			if string.find(citizenship, ';', 1, true) ~= nil
				then table.insert(output_categories, 'Multiple Citizenship')
			end
	end

	for i = 1, #list do
		value = h.trim(list[i])
		valueLower = string.lower(value)
		formerly = string.match(valueLower, 'formerly (.+)')
		annulled = string.match(valueLower, 'annulled (.+)')
		if formerly ~= nil
			then
				info = data[formerly]
				if info ~= nil
					then 
						value = h.LinkToCategory(info.d_categories[1], info.demonym) .. ' (formerly)'
						table.insert(output_categories, info.d_categories[1])
				end
			elseif annulled ~= nil
				then
					info = data[annulled]
					if info ~= nil
						then 
							value = h.LinkToCategory(info.d_categories[1], info.demonym) .. ' (annulled)'
							table.insert(output_categories, info.d_categories[1])
					end
			else	
				info = data[valueLower]
				if info ~= nil
					then
						value = h.LinkToCategory(info.d_categories[1], info.demonym)
						table.insert(output_categories, info.d_categories[1])
				end
		end
		table.insert(output, value)		
	end
	output = mw.text.listToText(output, ', ', ', ')

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_weight_value_and_unit(value)
	local list = {'lbs', 'ton', 'oz', 'kg'}
	local remainder = ''
	local unit = ''
	local output = value

	if string.find(value, 'Variable') ~= nil
		then 
			output = 'Variable'
			remainder = string.match(value, 'Variable(.*)')
		else
			for i = 1, 4 do
				unit = list[i]
				output, remainder = string.match(value, '(%d+.*%d*)'..unit..'(.*)')
				if output ~= nil
					then break
					else output = value
				end
			end
			if output == value
				then unit = ''
			end
	end
	
	if h.isempty(remainder)
		then remainder = ''
	end

	return output, unit, remainder
end


--------------------------------------------------------------------------------------------------
function p.get_weight(frame)
	local function lbs_to_kg(weightLbs)
		return h.round(weightLbs * units['lbs'].kg, 2)
	end
	local function get_weight_category(weightLbs)
		local i
		local output
		if weightLbs < 10
			then output = 'Weight 0-9 lbs ('..lbs_to_kg(10)..' kg)'
		elseif weightLbs >= 10 and weightLbs < 20
			then output = 'Weight 10-19 lbs ('..lbs_to_kg(10)..'-'..lbs_to_kg(20)..' kg)'
		elseif weightLbs >= 20 and weightLbs < 300
			then
				i = 20
				while i <= 280 do
					if weightLbs >= i and weightLbs < i + 20
						then output = 'Weight '..i..'-'..(i+19)..' lbs ('..lbs_to_kg(i)..'-'..lbs_to_kg(i+20)..' kg)'
					end
					i = i + 20
				end
		elseif weightLbs >= 300 and weightLbs < 1000
			then
				i = 300
				while i <= 900 do
					if weightLbs >= i and weightLbs < i + 100
						then output = 'Weight '..i..'-'..(i+99)..' lbs ('..lbs_to_kg(i)..'-'..lbs_to_kg(i+100)..' kg)'
					end
					i = i + 100
				end
    	elseif weightLbs >= 1000
    		then output = 'Weight above 1000 lbs ('..lbs_to_kg(1000)..' kg)'
    	end
    	return output
    end
	local args = getArgs (frame)
    local value = args.Weight
    --local value2 = args.Weight2
    local weight
    local weightLbs = 0
    local weightKg  = 0
	local unit = ""
	local remainder
	local i
	local category
	local list = {}
	local output_categories = {}
	local output = {}

	if not h.isempty(value)
		then
			list = h.explode(';', value)
			for i = 1, #list do
				weight, unit, remainder = p.get_weight_value_and_unit(list[i])
				if unit ~= ''
					then
						weightLbs = h.round( weight * units[unit].lbs , 2 )
						weightKg  = weight * units[unit].kg
						if weightKg < 1
        					then weightKg = h.round( weightKg * 1000, 2 ) .. " gram"
        					elseif weightKg > 1000
            					then weightKg = h.round( weightKg / 1000, 2 ) .. " ton"
        					else weightKg = h.round( weightKg, 2 ) .. " kg"
    					end
    					category = get_weight_category(weightLbs)
    					table.insert(output_categories, category)
    					table.insert(output, h.LinkToCategory(category, weightLbs .. " lbs (" .. weightKg .. ")")..remainder)
					elseif weight == 'Variable'
						then
							category = 'Variable Weight'
							table.insert(output_categories, category)
							table.insert(output, h.LinkToCategory(category, weight)..remainder)
					else
						table.insert(output, weight..remainder)
				end
			end
			output = mw.text.listToText(output, ',<br>', ',<br>')
	end

	--if not h.isempty(value2) 
	--	then output = output.." " ..value2
	--end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_height_value(value)
	local function round(m)
		return tonumber( string.format("%.2f", m) )
	end
	local function to_metres(metres)
		if metres < 1
			then return round(metres * 100)..' cm'
			elseif metres >= 1000
				then return round(metres / 1000)..' km'
			else return round(metres)..' m'
		end
	end
	local function to_feet_and_inches(feet, inches)
		if inches ~= '0'
			then return feet..'′'..inches..'″'
			else return feet..'′'
		end
	end
	
	local feet = string.match(value, "(%d+)'")
	local inches = string.match(value, '(%d+)"')
	local metres
	local remainder = ''
	local output = ''

	if feet ~= nil
		then
			if inches == nil
				then 
					inches = '0'
					metres = feet * units['ft'].m
					remainder = string.match(value, "%d+'(.+)")
				else
					metres = (feet * units['ft'].m) + (inches * units['in'].m)
					remainder = string.match(value, '%d+"(.+)')
			end
			output = to_feet_and_inches(feet, inches)..' ('..to_metres(metres)..')'
		elseif string.find(value, 'Variable') ~= nil
			then 
				output = 'Variable'
				remainder = string.match(value, 'Variable(.*)')
		else
			feet = string.match(value, "(%d+) mile")
			if feet ~= nil
				then
					output = string.match(value, "%d+ mile[s]?")..' ('..round(feet * 1.609) ..' km)'
					remainder = string.match(value, "%d+ mile[s]? (.+)")
					feet = 1000 -- value to sort height into correct category
			end
		
			metres = string.match(value, "(%d+) cm")
			if metres
				then
					inches = metres/100 / units['in'].m
					feet = math.floor(inches / 12)
					inches = math.floor(inches - 12*feet)
					output = to_feet_and_inches(feet, inches)..' ('..to_metres(metres/100)..')'
					remainder = string.match(value, "%d+ cm(.+)")
			end
	end
	if h.isempty(remainder)
		then remainder = ''
	end

	return output, remainder, feet, inches
end


--------------------------------------------------------------------------------------------------
function p.get_height(frame)
	local function ft_to_m(heightFt)
		return tonumber( string.format("%.2f", heightFt * units['ft'].m) )
	end
	local function get_height_category(heightFt, heightIn)
		local output
		heightFt = tonumber(heightFt)
		if heightFt < 1
			then output = 'Height 0-1 ft. ('..ft_to_m(1)..' m)'
    	elseif heightFt >= 1 and heightFt < 2
    		then output = 'Height 1-2 ft. ('..ft_to_m(1)..'-'..ft_to_m(2)..' m)'
    	elseif heightFt >= 2 and heightFt < 3
    		then output = 'Height 2-3 ft. ('..ft_to_m(2)..'-'..ft_to_m(3)..' m)'
    	elseif heightFt >= 3 and heightFt < 4
    		then output = 'Height 3-4 ft. ('..ft_to_m(3)..'-'..ft_to_m(4)..' m)'
    	elseif heightFt >= 4 and heightFt < 5
    		then output = 'Height 4-5 ft. ('..ft_to_m(4)..'-'..ft_to_m(5)..' m)'
    	elseif heightFt >= 5 and heightFt < 6
    		then output = 'Height 5 ft. '..heightIn..' in. ('..ft_to_m(5 + heightIn/12)..' m)'
    	elseif heightFt >= 6 and heightFt < 7
    		then output = 'Height 6 ft. '..heightIn..' in. ('..ft_to_m(6 + heightIn/12)..' m)'
    	elseif heightFt >= 7 and heightFt < 8
    		then output = 'Height 7-8 ft. ('..ft_to_m(7)..'-'..ft_to_m(8)..' m)'
    	elseif heightFt >= 8
    		then output = 'Height above 8 ft. ('..ft_to_m(8)..' m)'
    	end
    	return output
    end
	local args = getArgs (frame)
    local value = args.Height
    local value2 = args.Height2
    local height
    local height_ft
    local height_in
	local remainder
	local i
	local category
	local list = {}
	local output_categories = {}
	local output = {}

	if not h.isempty(value)
		then
			list = h.explode(';', value)
			for i = 1, #list do
				height, remainder, height_ft, height_in  = p.get_height_value(list[i])
				if height ~= ''
					then
						if height_ft ~= nil
							then category = get_height_category(height_ft, height_in)
							elseif height == 'Variable'
								then category = 'Variable Height'
						end
    					table.insert(output_categories, category)
    					table.insert(output, h.LinkToCategory(category, height)..remainder)
					else
						table.insert(output, height..remainder)
				end
			end
			output = mw.text.listToText(output, ',<br>', ',<br>')
	end

	--if not h.isempty(value2) 
	--	then output = output.." " ..value2
	--end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_eyes(frame)
	local function iris_color(value, remainder, former_color, original_color, glowing_color)
		local category = ''
		local output = ''
		if not h.isempty(value)
			then
				if value == 'No Iris'
					then
						if former_color == true or original_color == true
							then 
								category = 'Formerly No Visible Irises or Pupils'
								if former_color == true
									then output = h.LinkToCategory(category, 'No Visible')..remainder..' (formerly)'
									else output = 'originally '..h.LinkToCategory(category, 'No Visible')..remainder
								end
							else
								category = 'No Visible Irises or Pupils'
								output = h.LinkToCategory(category, 'No Visible')..remainder
						end
					else
						output, category = p.get_color_value_and_category(value, remainder, former_color, original_color, glowing_color, ' Eyes')
				end
		end
		return output, category
	end
	local args = getArgs (frame)
	local eyes = args.Eyes
	local eyeballs = args.Eyeballs
	local list = {}
	local category = ''
	local value
	local remainder = ''
	local former_color = false
	local original_color = false
	local glowing_color = false
	local i
	local output_categories = {}
	local output = ''
	
	if not h.isempty(eyes)
		then
			list = h.explode(';', eyes)
			eyes = {}
			for i = 1, #list do
				value, remainder, former_color, original_color, glowing_color = p.get_values_for_color_and_remainder(h.trim(list[i]), {}, ' Eyes')
				value, category = iris_color(value, remainder, former_color, original_color, glowing_color)
				table.insert(output_categories, category)
				table.insert(eyes, '\n* '..value)
			end
			eyes = mw.text.listToText(eyes, '', '')
			output = '\n'..design.span('Irises:').bold..eyes
	end

	if not h.isempty(eyeballs)
		then
			list = h.explode(';', eyeballs)
			eyeballs = {}
			for i = 1, #list do
				value, remainder, former_color, original_color, glowing_color = p.get_values_for_color_and_remainder(h.trim(list[i]), {}, ' Eyeballs')
				value, category = p.get_color_value_and_category(value, remainder, former_color, original_color, glowing_color, ' Eyeballs')
				table.insert(output_categories, category)
				table.insert(eyeballs, '\n* '..value)
			end
			eyeballs = mw.text.listToText(eyeballs, '', '')
			output = '\n'..design.span('Eyeballs:').bold..eyeballs..output
	end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_hair(frame)
	local function dyed_hair(value)
		local dyed = false
		local output = value
		if string.find(value, 'Dyed ') ~= nil
			then
				dyed = true
				output = string.gsub(value, 'Dyed ', '')
		end
		return output, dyed
	end
	local function hair_color(value, dyed, former_color, original_color, glowing_color, remainder)
		local glowing
		local category = ''
		local output_categories = {}
		local output = ''
		if not h.isempty(value)
			then
				if glowing_color == true
					then glowing = 'Glowing '..value
					else glowing = value
				end
				value = string.gsub(value, 'Blonde', 'Blond')
				value = string.gsub(value, 'Fair', 'Blond')
				value = string.gsub(value, 'Reddish Brown', 'Auburn')
				value = string.gsub(value, 'Greying', 'Grey-haired')
				if former_color == true or original_color == true
					then category = 'Formerly '
					elseif dyed == true
						then category = 'Dyed '
				end
				if value == 'Bald'
					then category = category..'Bald'
					elseif value == 'Balding'
						then category = category..'Balding'
					elseif value == 'Grey-haired'
						then category = category..'Grey-haired'		
					else category = category..value..' Hair'
				end
				if value == 'No Hair'
					then 
						if former_color == true or original_color == true
							then category = 'Formerly No Hair'
							else category = 'No Hair'
						end
						output = h.LinkToCategory(category, 'No Hair At All')..remainder
					elseif dyed == true
						then 
							output = h.LinkToCategory(category, 'Dyed '..glowing)..remainder
							if former_color == true
								then output = output..' (formerly)'
							end
					elseif former_color == true
						then output = h.LinkToCategory(category, glowing)..remainder..' (formerly)'
					elseif original_color == true
						then output = 'originally '..h.LinkToCategory(category, glowing)..remainder
					else
						output = h.LinkToCategory(category, glowing)..remainder
				end
				table.insert(output_categories, category)
		end
		return output, output_categories
	end
	local args = getArgs (frame)
	local hair = args.Hair
	local exceptions_list = {'dark blond', 'light brown', 'platinum blond', 'strawberry blond'}
	local category = ''
	local value = ''
	local remainder
	local dyed = false
	local former_color = false
	local original_color = false
	local glowing_color = false
	local i
	local list = {}
	local output_categories = {}
	local output = ''
	
	if not h.isempty(hair)
		then
			output = {}
			list = h.explode(';', hair)
			for i = 1, #list do
				value, dyed = dyed_hair(h.trim(list[i]))
				value, remainder, former_color, original_color, glowing_color = p.get_values_for_color_and_remainder(value, exceptions_list, ' Hair')
				value, category = hair_color(value, dyed, former_color, original_color, glowing_color, remainder)
				output_categories = h.join_tables(output_categories, category)
				table.insert(output, value)
			end
			output = mw.text.listToText(output, ',<br>', ',<br>')
	end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_skin(frame)
	local args = getArgs (frame)
	local skin = args.Skin
	local exceptions_list = {'light green', 'green, yellow'}
	local category = ''
	local value
	local remainder = ''
	local former_color = false
	local original_color = false
	local glowing_color = false
	local i
	local list = {}
	local output_categories = {}
	local output = ''
	
	if not h.isempty(skin)
		then
			output = {}
			list = h.explode(';', skin)
			for i = 1, #list do
				value, remainder, former_color, original_color, glowing_color = p.get_values_for_color_and_remainder(h.trim(list[i]), exceptions_list, ' Skin')
				value, category = p.get_color_value_and_category(value, remainder, former_color, original_color, glowing_color, ' Skin')
				table.insert(output_categories, category)
				table.insert(output, value)
			end
			output = mw.text.listToText(output, ',<br>', ',<br>')
	end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_gender(frame)
	local function get_value_and_remainder(value)
		local i
		local remainder = ''

		i = string.find(value, ' ')
		if i ~= nil
			then 
				remainder = ' '..string.sub(value, i+1, #value)
				value = string.sub(value, 1, i-1)
		end

		return value, remainder
	end
	local function gender_category(value)
		local category = ''
		local output_categories = {}
		local output = ''
		if not h.isempty(value)
			then
				category = value..' Characters'
				if h.pages_in_category(category, 'pages') > 0
					then output = h.LinkToCategory(category, value)
					else category = ''
				end
				table.insert(output_categories, category)
		end
		return output, output_categories
	end
	local args = getArgs (frame)
	local gender = args.Gender
	--local gender2 = args.Gender2
	local category = ''
	local value
	local remainder
	local i
	local list = {}
	local output_categories = {}
	local output = ''
	
	if not h.isempty(gender)
		then
			output = {}
			list = h.explode(';', gender)
			for i = 1, #list do
				value, remainder = get_value_and_remainder(h.trim(list[i]))
				value, category = gender_category(value)
				output_categories = h.join_tables(output_categories, category)
				table.insert(output, value..remainder)
			end
			output = mw.text.listToText(output, ',<br>', ',<br>')
	end
	--if not h.isempty(gender2)
	--	then output = output..' '..gender2
	--end

	return output..h.add_categories(output_categories)
end


--------------------------------------------------------------------------------------------------
function p.get_unusual_features(frame)
	local args = getArgs (frame)
	local value = args.UnusualFeatures
	local list = require('Module:CharacterInfoboxUnusualFeatures')
	local output_categories = p.get_categories_from_keywords(value, list, {})
	output_categories = h.add_categories(output_categories)
	if string.find(output_categories, 'Prehensile Tail') ~= nil
		then output_categories = string.gsub(output_categories, '%[%[Category:Tail%]%]', '')
	end
	
	return design.add_infobox_row_collapsible({value})..output_categories
end


--------------------------------------------------------------------------------------------------
function p.get_origin(frame)
	local args = getArgs (frame)
	local list = require('Module:CharacterInfoboxOrigins')
	local value = args.Origin
	local output = ''
	
	if not h.isempty(value)
		then 
			output = p.get_categories_from_keywords(value, list.valid, list.exceptions)
			value = design.add_infobox_row_collapsible({value})
			output = value..h.add_categories(output)
	end
	
	return output
end


--------------------------------------------------------------------------------------------------
function p.get_categories_from_keywords(value, valid_list, exceptions_list)
	local function find_value(str, value)
		return string.find(str, '[%s%[%|%(]'..value..'[s%]%p%s%)]') ~= nil
				or string.find(str, '^'..value..'[s%]%p%s%)]') ~= nil
				or string.find(str, '[%s%[%|%(]'..value..'$') ~= nil
				or string.find(str, '^'..value..'$') ~= nil
	end
	local lower_value
	local exception_values
	local valid
	local output = {}

	if not h.isempty(value) -- Field isn't blank
		then -- Grab a valid pair and use it
			lower_value = string.lower(value)
			for validKey, validValue in pairs(valid_list) do
				exception_values = exceptions_list[validKey]
				-- If you find the validKey in the field, and there are no exceptions for this validKey, then categorize
				if find_value(lower_value, validKey) 
					then
						valid = true
						if exception_values ~= nil
							then
								for valueKey, valueName in ipairs( exception_values ) do
									if find_value(lower_value, valueName)
										then valid = false
									end
								end
						end
						if valid
							then
								for valueKey, valueCategoryName in ipairs( validValue ) do
									table.insert(output, valueCategoryName)
								end
						end
				end
			end
			table.sort(output)
	end
	return output
end


--------------------------------------------------------------------------------------------------
function p.get_values_for_color_and_remainder(value, exceptions_list, s_type)
	local i
	local j
	local k
	local remainder = ''
	local former_color = false
	local original_color = false
	local glowing_color = false
	
	exceptions_list = h.join_tables(exceptions_list, string.lower('No'..s_type)) -- exception for 'No <s_type>' to not treat it is as value='No'  and comment='<s_type>'
	exceptions_list = h.join_tables(exceptions_list, 'no iris') -- exception for 'No Iris' to not treat it is as value='No'  and comment='Iris'
	
	i, j = string.find(value, 'Formerly ')
	if j ~= nil
		then
			former_color = true
			value = string.gsub(value, 'Formerly ', '')
	end

	i, j = string.find(value, 'Originally ')
	if j ~= nil
		then
			original_color = true
			value = string.gsub(value, 'Originally ', '')
	end

	i, j = string.find(value, 'Glowing ')
	if j ~= nil
		then
			glowing_color = true
			value = string.gsub(value, 'Glowing ', '')
	end

	value = string.gsub(value, '^n%/a', 'No')
	value = string.gsub(value, '^None', 'No')
	value = string.gsub(value, '^No'..s_type, 'No')
	if string.find(value, 'No Iris') == nil -- exception for 'No Iris' to not change it to 'No Eyes'
		then value = string.gsub(value, '^No', 'No'..s_type)
	end

	for k = 1, #exceptions_list do
		i, j = string.find(string.lower(value), exceptions_list[k])
		if j ~= nil
			then 
				i = string.find(value, ' ', j)
				break
			else i = string.find(value, ' ')
		end
	end

	if i ~= nil
		then 
			remainder = ' '..string.sub(value, i+1, #value)
			value = string.sub(value, 1, i-1)
	end

	value = string.gsub(value, 'Gray', 'Grey')
	
	return value, remainder, former_color, original_color, glowing_color
end

--------------------------------------------------------------------------------------------------
function p.get_color_value_and_category(value, remainder, former_color, original_color, glowing_color, s_type)
	local glowing 
	local category = ''
	local output = ''
	
	if not h.isempty(value)
		then
			if value == 'No'..s_type
				then
					if former_color == true or original_color == true
						then 
							category = 'Formerly '..value
							if former_color == true
								then output = h.LinkToCategory(category, value..' At All')..remainder..' (formerly)'
								else output = 'originally '..h.LinkToCategory(category, value..' At All')..remainder
							end
						else
							category = value
							output = h.LinkToCategory(category, value..' At All')..remainder
					end
				else
					if glowing_color == true
						then glowing = 'Glowing '..value
						else glowing = value
					end
					if former_color == true or original_color == true
						then 
							category = 'Formerly '..value..s_type
							if former_color == true
								then output = h.LinkToCategory(category, glowing)..remainder..' (formerly)'
								else output = 'originally '..h.LinkToCategory(category, glowing)..remainder
							end
						else
							category = value..s_type
							output = h.LinkToCategory(category, glowing)..remainder
					end
			end
	end
	
	return output, category
end

--------------------------------------------------------------------------------------------------
function p.get_hosts(frame)
	local args = getArgs (frame)
	local list = require('Module:Host')
	local output_categories = {}
	local output = args.HostOf

	for key, value in pairs(list) do
		if string.find(output, '[['..key, 1, true) ~= nil
			then output_categories = h.join_tables(output_categories, value)
		end
	end
	if string.find(table.concat(output_categories), 'Symbiote') == nil and string.find(string.lower(output), 'symbiote') ~= nil
		then table.insert(output_categories, 'Hosts of Symbiotes')
	end
	
	return design.add_infobox_row_collapsible({output})..h.add_categories(output_categories)
end
	
return p