Documentation for this module may be created at Module:Infobox/doc

local PROP_CONNECTS_WITH = 'P2789'
local PROP_COORDINATES = 'P625';
local PROP_HOUSENUMBER = 'P670'
local PROP_IMAGE = 'P18'
local PROP_INCEPTION = 'P571'
local PROP_DEMOLITION = 'P576'
local PROP_INHERIT_PLACE_NO = 'P2618'
local PROP_LOCATED_ON_STREET = 'P669'
local PROP_LOCATION = 'P276'
local PROP_NAMED_AFTER = 'P138'
local PROP_OSM_RELATION = 'P402'
local PROP_DATE_OF_BIRTH = 'P569'
local PROP_PLACE_OF_BIRTH = 'P19'
local PROP_DATE_OF_DEATH = 'P570'
local PROP_PLACE_OF_DEATH = 'P20'
local PROP_FATHER = 'P22'
local PROP_MOTHER = 'P25'
local PROP_WABI = 'P13035'
local PROP_WIKITREE = 'P2949'
local PROP_FAMILYSEARCH = 'P2889'

local p = {}

local function getRow( header, value, rowClass )
	if value == nil or value == '' or value == 'nil' then
		return ''
	end

	-- Header.
	local th = mw.html.create( 'th' )
	th:wikitext( header )

	-- Data.
	local td = mw.html.create( 'td' )
	td:wikitext( value )

	-- Row.
	local row = mw.html.create( 'tr' )
	if rowClass ~= nil then
		row:attr( 'class', rowClass )
	end
	if header ~= nil then
		row:node( th )
	else
		td:attr( 'colspan', '2' )
	end
	row:node( td )
	return row
end

function getItem( args )
	local item = nil
	local itemLink = ''
	if args.wikidata ~= nil and args.wikidata ~= '' then
		item = mw.ext.UnlinkedWikibase.getEntity( args.wikidata );
		if item ~= nil and item.id ~= nil then
			itemLink = '[[d:' .. item.id .. '|' .. item.id .. ']]'
		else
			itemLink = '<span class="error">Wikidata item ' .. args.wikidata .. ' not found.</span>'
		end
	end
	return {
		item = item,
		link = itemLink
	}
end

function getImage( args, item )
	local imageFilename = 'Pictures Not Yet Available.svg'
	local imageLink = nil
	local imageClass = 'mdl-infobox-image-not-found'
	if args.image ~= nil and args.image ~= '' then
		imageFilename = args.image
		imageClass = ''
	elseif item and item.claims and item.claims[PROP_IMAGE] ~= nil then
		imageFilename = item.claims[PROP_IMAGE][1].mainsnak.datavalue.value
		imageClass = ''
	end
	imageLink = '[[File:'..imageFilename..'|300x300px|center|class=mdl-infobox-image ' .. imageClass .. ']]'
	if args.caption ~= nil and args.caption ~= '' then
		imageLink = imageLink .. '<span class="mdl-infobox-caption">' .. args.caption .. '</span>'
	end
	return {
		link = imageLink,
		filename = imageFilename
	}
end

p.dateAndPlace = function ( item, dateProp, placeProp )
	local out = {}

	-- Dates.
	local datesData = item.claims[dateProp] or {}
	local dates = {}
	local lang = mw.language.getContentLanguage()
	for _,claim in pairs( datesData ) do
		-- Precision: 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day
		local format = 'Y'
		if claim.mainsnak.datavalue.value.precision == 10 then
			format = 'F Y'
		elseif claim.mainsnak.datavalue.value.precision == 11 then
			format = 'j F Y'
		end
		dates[#dates + 1] = lang:formatDate(format, claim.mainsnak.datavalue.value.time )
		out.date = lang:formatDate('Y-m-d', claim.mainsnak.datavalue.value.time )
		out.date_precision = 'day'
	end
	local allDates = table.concat( dates, ' or ' )

	-- Places.
	local placesClaims = item.claims[placeProp] or {}
	local places = {}
	for _,claim in pairs( placesClaims ) do
		places[#places + 1] = '{{wdl|' .. claim.mainsnak.datavalue.value.id .. '}}'
		out.place = claim.mainsnak.datavalue.value.id
	end
	local allPlaces = table.concat( places, ' or ' )

	-- Put them together.
	if allDates ~= '' and allPlaces ~= '' then
		out.display = allDates .. ' in ' .. allPlaces
	else
		out.display = allDates .. allPlaces
	end

	return out
end

function getMap( args, item, query )
	local center = nil
	if args.coordinates ~= nil and args.coordinates ~= '' then
		center = args.coordinates
	elseif item and item.claims ~= nil and item.claims[PROP_COORDINATES] ~= nil then
		center = item.claims[PROP_COORDINATES][1].mainsnak.datavalue.value.latitude .. ', '
			.. item.claims[PROP_COORDINATES][1].mainsnak.datavalue.value.longitude
	end
	local showLink = true
	if center == nil then
		center = mw.title.getCurrentTitle().text .. ', Fremantle, Western Australia, Australia'
		showLink = false
	end
	local mapParams = {
		getMapQueryResults( query ),
		center = center,
		zoom = '16',
		height = '400px'
	}
	local map = mw.getCurrentFrame():callParserFunction{ name = '#display_map', args = mapParams }
	local link = ''
	if showLink then
		local geohackUrl = 'https://geohack.toolforge.org/geohack.php'
		local linkCoords = mw.ustring.gsub( mw.ustring.gsub( center, ',', ';' ), '[^0-9-.;]', '' )
		link = '<span class="mdl-infobox-geohack">'
			.. '[' .. geohackUrl .. '?params=' .. linkCoords .. '&title=Freopedia '
			.. center .. ']</span>'
	end
	return map .. link
end

function getMapQueryResults( query )
	if query == nil then
		return ''
	end
	local res = mw.ext.UnlinkedWikibase.query( query )
	if res == nil or res.results == nil or res.results.bindings == nil then
		return ''
	end
	local out = ''
	for k,v in pairs( res.results.bindings ) do
		local itemId = string.sub( v.item.value, 32 )
		local item = mw.ext.UnlinkedWikibase.getEntity( itemId )
		if item.claims ~= nil and item.claims[PROP_COORDINATES] ~= nil then
			out = out .. '; '
				.. item.claims[PROP_COORDINATES][1].mainsnak.datavalue.value.latitude .. ','
				.. item.claims[PROP_COORDINATES][1].mainsnak.datavalue.value.longitude
				.. '~[[' .. item.labels.en.value .. ']]'
		end
	end
	return out
end

function getEnglishWikipediaTitle( item )
	local wikipedia = nil
    if item.item ~= nil and item.item.sitelinks ~= nil and item.item.sitelinks.enwiki ~= nil then
    	wikipedia = item.item.sitelinks.enwiki.title
    end
    return wikipedia
end

function getEnglishWikipediaBanner( item )
    local enwikiBanner = ''
    if item.item ~= nil and item.item.sitelinks ~= nil and item.item.sitelinks.enwiki ~= nil then
    	enwikiBanner = mw.getCurrentFrame():expandTemplate{ title = 'wikipedia', args = { item.item.sitelinks.enwiki.title } }
    end
    return enwikiBanner
end

function getYear( args, item, arg, prop )
	local year = nil
	if args[arg] ~= nil then
		year = args[arg]
	elseif item.item and item.item.claims ~= nil and item.item.claims[prop] ~= nil and item.item.claims[prop][1].mainsnak.datavalue ~= nil then
		-- Get the year (only) of the inception time.
		local time = item.item.claims[prop][1].mainsnak.datavalue.value.time
		year = string.sub( time, 2, 5 )
	end
	return year
end

p.street = function( frame )
	-- Wikidata item.
	local item = getItem( frame.args )

	-- Locations/suburbs.
	local locations = {}
	if item.item and item.item.claims ~= nil and item.item.claims[PROP_LOCATION] ~= nil then
		for k,v in pairs( item.item.claims[PROP_LOCATION] ) do
			local locationId = v.mainsnak.datavalue.value.id
			local locPage = mw.ext.UnlinkedWikibase.getLocalTitle( locationId )
			local locPageTitle = ''
			if locPage ~= nil then
				locPageTitle = locPage.text
			else
				local locPageItem = mw.ext.UnlinkedWikibase.getEntity( locationId )
				locPageTitle = locPageItem.labels.en.value
			end
			-- listToText wants tables to be 1-indexed.
			if isProse then
				locations[k] = '[[' .. locPageTitle .. ']]'
			else
				locations[k] = locPageTitle
			end
		end
	end
	if frame.args and frame.args.suburbs ~= nil and frame.args.suburbs ~= '' then
		locations = mw.text.split( frame.args.suburbs, ';', true )
	end
	local locationLinks = ''
	if #locations > 0 then
		locationLinks = '<ol><li>[[' .. table.concat( locations, ']]</li><li>[[' ) .. ']]</li></ol>'
	end
	
	-- OSM Relation
	local osmRelation = nil
	local osmLink = nil
	if item.item and item.item.claims ~= nil and item.item.claims[PROP_OSM_RELATION] ~= nil then
		osmRelation = item.item.claims[PROP_OSM_RELATION][1].mainsnak.datavalue.value
		osmLink = '[https://www.openstreetmap.org/relation/' .. osmRelation .. ' ' .. osmRelation .. ']'
	end

	local image = getImage( frame.args, item.item )

	-- Named after
	local namedAfter = nil
	local namedAfterLink = nil
	if item.item and item.item.claims ~= nil and item.item.claims[PROP_NAMED_AFTER] ~= nil then
		local namedAfterId = item.item.claims[PROP_NAMED_AFTER][1].mainsnak.datavalue.value.id
		local namedAfterItem = mw.ext.UnlinkedWikibase.getEntity( namedAfterId )
		namedAfter = namedAfterItem.labels.en.value
		namedAfterLink = '[[' .. namedAfter .. ']]'
	end
	
	-- Connects with.
	local connectsWith = {}
	if item.item and item.item.claims ~= nil and item.item.claims[PROP_CONNECTS_WITH] ~= nil then
		for k,v in pairs( item.item.claims[PROP_CONNECTS_WITH] ) do
			local connectsWithId = v.mainsnak.datavalue.value.id
			local connectsWithItem = mw.ext.UnlinkedWikibase.getEntity( connectsWithId )
			connectsWith[k] = connectsWithItem.labels.en.value
		end
	end
	local connectsWithList = nil
	if #connectsWith > 0 then
		connectsWithList = '<ol><li>[[' .. table.concat( connectsWith, ']]</li><li>[[' ) .. ']]</li></ol>'
	end

	-- Store data in Cargo.
	mw.getCurrentFrame():callParserFunction( '#cargo_store', {
		'_table=streets',
		wikidata = frame.args.wikidata,
		image = image.filename,
		named_after = namedAfter,
		name_etymology = frame.args.name_etymology,
		suburbs = table.concat( locations, '; ' ),
		osm_relation = osmRelation,
		wikipedia = getEnglishWikipediaTitle( item )
	} )

	-- local mapQuery = nil
	-- if item.item ~= nil and item.item.id ~= nil then
	-- 	mapQuery = "SELECT ?item WHERE { ?item wdt:P669 wd:" .. item.item.id .. " }"
	-- end

	-- Put the whole infobox together
	local styles = mw.getCurrentFrame():extensionTag( 'templatestyles', nil, { src = 'Module:Infobox/styles.css' } )
	local infobox = mw.html.create( 'table' )
	infobox:attr( 'class', 'infobox' )
	infobox:node( getRow( nil, image.link ) )
	infobox:node( getRow( 'Suburbs:', locationLinks ) )
	infobox:node( getRow( 'Named after:', namedAfterLink ) )
	infobox:node( getRow( 'Connects with:', connectsWithList ) )
	infobox:node( getRow( 'Wikidata:', item.link ) )
	infobox:node( getRow( 'OpenStreetMap:', osmLink ) )
	infobox:node( getRow( nil, getMap( frame.args, item.item ) ) )
	return styles .. tostring( infobox ) .. getEnglishWikipediaBanner( item )
end

p.building = function( frame )
	local item = getItem( frame.args )
	local image = getImage( frame.args, item.item )

	-- Streets.
	local streets = {}
	if frame.args.streets ~= nil then
		streets = mw.text.split( frame.args.streets, ';', true )
	end
	if item.item and item.item.claims ~= nil and item.item.claims[PROP_LOCATED_ON_STREET] ~= nil then
		for k,v in pairs( item.item.claims[PROP_LOCATED_ON_STREET] ) do
			local streetId = v.mainsnak.datavalue.value.id
			local localStreetTitle = mw.ext.UnlinkedWikibase.getLocalTitle( streetId )
			local localStreet = ''
			if localStreetTitle ~= nil then
				localStreet = localStreetTitle.text
			else
				local streetItem = mw.ext.UnlinkedWikibase.getEntity( streetId )
				localStreet = streetItem.labels.en.value
			end
			streets[k] = localStreet
		end
	end
	local streetList = ''
	if #streets > 0 then
		streetList = '<ol><li>[[' .. table.concat( streets, ']]</li><li>[[' ) .. ']]</li>'
	end
	
	-- Inception and dissolved, abolished or demolished dates.
	local inception = getYear( frame.args, item, 'inception', PROP_INCEPTION )
	local demolition = getYear( frame.args, item, 'demolition', PROP_DEMOLITION )

	-- inHerit place number
	local inheritLink = nil
	if item.item and item.item.claims ~= nil and item.item.claims[PROP_INHERIT_PLACE_NO] ~= nil then
		local inheritNo = item.item.claims[PROP_INHERIT_PLACE_NO][1].mainsnak.datavalue.value
		inheritLink = '[http://inherit.stateheritage.wa.gov.au/public/p/' .. inheritNo .. ' ' .. inheritNo .. ']'
	end

	-- Store data in Cargo.
	local cargoStore = mw.getCurrentFrame():callParserFunction( '#cargo_store', {
		'_table=buildings',
		wikidata = frame.args.wikidata,
		image = image.filename,
		streets = table.concat( streets, '; ' ),
		inception = inception,
		demolition = demolition
	} )

	-- Construct the infobox.
	local styles = mw.getCurrentFrame():extensionTag( 'templatestyles', nil, { src = 'Module:Infobox/styles.css' } )
	local infobox = mw.html.create( 'table' )
	infobox:attr( 'class', 'infobox' )
	infobox:node( getRow( nil, image.link ) )
	infobox:node( getRow( 'Streets:', streetList ) )
	infobox:node( getRow( 'Built:', inception ) )
	infobox:node( getRow( 'Demolished:', demolition ) )
	infobox:node( getRow( 'Wikidata:', item.link ) )
	infobox:node( getRow( 'inHerit:', inheritLink ) )
	infobox:node( getRow( nil, getMap( frame.args, item.item, mapQuery ) ) )
	return styles .. tostring( infobox ) .. getEnglishWikipediaBanner( item )
end

function getPersonClaim( args, argname, propId, format )
	local propVal = nil
	if args[ argname ] ~= nil and args[ argname ] ~= '' then
		propVal = args[ argname ]
	elseif propId ~= nil and args.wikidata then
		local item = mw.ext.UnlinkedWikibase.getEntity( args.wikidata )
		if item and item.claims ~= nil and item.claims[ propId ] ~= nil then
			propVal = item.claims[ propId ][ 1 ].mainsnak.datavalue.value
		end
	end

	-- Return the required formatted output.
	if propVal == nil then
		return ''
	elseif format ~= nil and format == 'link' then
		if argname == 'wikitree' then
			return '<br />[[WikiTree]]: [https://www.wikitree.com/wiki/' .. propVal .. ' ' .. propVal .. ']'
		elseif argname == 'wabi' then
			return '<br />[[WABI]]: [https://slwa.wa.gov.au/data/wabi/' .. propVal .. '.jpg ' .. propVal .. ']'
		elseif argname == 'familysearch' then
			return '<br />[[FamilySearch]]: [https://www.familysearch.org/tree/person/details/' .. propVal .. ' ' .. propVal .. ']'
		elseif argname == 'ancestry' then
			return '<br />[[Ancestry]]: [https://www.ancestry.com/family-tree/person/tree/191471830/person/' .. propVal .. '/facts ' .. propVal .. ']'
		end
	else
		return propVal
	end
end

p.person = function( frame )
	local item = getItem( frame.args )
	local image = getImage( frame.args, item.item )
	local birth = ''
	local death = ''
	if item.item ~= nil then
		birth = p.dateAndPlace( item.item, PROP_DATE_OF_BIRTH, PROP_PLACE_OF_BIRTH )
		death = p.dateAndPlace( item.item, PROP_DATE_OF_DEATH, PROP_PLACE_OF_DEATH )
	end

	-- Store data in Cargo.
	local cargoStore = mw.getCurrentFrame():callParserFunction( '#cargo_store', {
		'_table=people',
		wikidata = frame.args.wikidata,
		image = image.filename,
		name = frame.args.name,
		description = frame.args.description,
		birth_date = birth.date,
		birth_date_precision = birth.date_precision,
		birth_place = birth.place,
		death_date = death.date,
		death_date_precision = death.date_precision,
		death_place = death.place,
		wikidata = frame.args.wikidata,
		wikipedia = getEnglishWikipediaTitle( item ),
		wikitree = getPersonClaim( frame.args, 'wikitree', PROP_WIKITREE ),
		familysearch = getPersonClaim( frame.args, 'familysearch', PROP_FAMILYSEARCH ),
		ancestry = frame.args.ancestry
	} )

	-- Construct the infobox.
	local styles = mw.getCurrentFrame():extensionTag( 'templatestyles', nil, { src = 'Module:Infobox/styles.css' } )
	local infobox = mw.html.create( 'table' )
	infobox:attr( 'class', 'infobox' )
	infobox:node( getRow( nil, image.link ) )
	infobox:node( getRow( 'Birth:', mw.getCurrentFrame():preprocess( birth.display ) ) )
	infobox:node( getRow( 'Death:', mw.getCurrentFrame():preprocess( death.display ) ) )
	local authControl = ''
	if item.link then
		authControl = authControl .. 'Wikidata: ' .. item.link
	end
	authControl = authControl .. getPersonClaim( frame.args, 'wikitree', PROP_WIKITREE, 'link' )
		 .. getPersonClaim( frame.args, 'wabi', PROP_WABI, 'link' )
		 .. getPersonClaim( frame.args, 'familysearch', PROP_FAMILYSEARCH, 'link' )
		 .. getPersonClaim( frame.args, 'ancestry', nil, 'link' )
	infobox:node( getRow( 'Authority control:', authControl, 'mdl-infobox-authority-control' ) )
	return styles .. tostring( infobox ) .. getEnglishWikipediaBanner( item )
end

-- Testing:
-- 
-- Bannister Street =p.street({args={wikidata='Q29027032'}})
-- Hampton Road =p.street({args={wikidata='Q5646280'}})
-- South Terrace =p.street({args={wikidata='Q7568626'}})
--
-- Ada Street =p.building({args={wikidata="Q85379546"}})
-- x Henville Street =p.building({args={wikidata="Q72999302"}})
-- Dalkeith House =p.building({args={wikidata="Q66975054"}})
--
-- Frederick Samson =p.person({args={wikidata="Q5498696"}})
-- James Nias Croke =p.person({args={wikidata="Q64876076", ancestry="12345"}})

return p