Documentation for this module may be created at Модуль:Wikidata/chronology/doc

local p = {} -- p stands for package

-- technical functions for takeAdjacentNumbersFromStrings
function isYear(number) -- TODO: settings not hardcoded
	return number >= 1800 and number <= 2100
end

function isAr(number)
	return number >= 0 and number < 100
end

function notTooFar(number1, number2, maximalPeriod)
	return number2 - number1 > 0 and number2 - number1 <= maximalPeriod
end

-- takes two strings and returns either two substrings that form the only difference between them or nil
-- considers several possible formats of substrings given in options.formats
function takeAdjacentNumbersFromStrings(options, string1, string2)
	if type(string1) == 'string' and type(string2) == 'string' then
		local formats = options.formats or '' -- TODO: not repeat with properties
		if formats == '' then
			formats = 'year, year/ar, year/year'
		elseif formats == '-' then
			formats = ''
		end
		local formats = mw.text.split(formats, ', ?')
		
		local line = '!' .. string1 .. '!!' .. string2.. '!' -- -- TODO: without hacks
		local maximalPeriod = tonumber(options.maximalPeriod) or 4 -- TODO: here or in main function?
		
		for _, format in pairs(formats) do
			if format == 'year' or format == 'number' then
				local match = {mw.ustring.match(line, '^(.*%D)(%d+)(%D.*)%1(%d+)%3$')}
				if #match > 0 then
					local number1 = tonumber(match[2])
					local number2 = tonumber(match[4])
					
					if notTooFar(number1, number2, maximalPeriod) then
						if (format == 'year' and isYear(number1) and isYear(number2))
							or (format == 'number')
						then
							return {number1, number2}
						end
					end
				end
			elseif format == 'year/ar' or format == 'year/year' then
				local match = {mw.ustring.match(line, '^(.*%D)((%d+)[-–\/](%d+))(%D.*)%1((%d+)\/(%d+))%5$')}
				if #match > 0 then
					local period1 = match[2]
					local period1_start = tonumber(match[3])
					local period1_end = tonumber(match[4])
					local period2 = match[6]
					local period2_start = tonumber(match[7])
					local period2_end = tonumber(match[8])
					
					if notTooFar(period1_start, period2_start, maximalPeriod) and isYear(period1_start) and isYear(period2_start)
						and period2_start - period1_start == period2_end - period1_end
					then
						if (format == 'year/ar' and isAr(period1_end) and isAr(period2_end))
							or (format == 'year/year' and isYear(period1_end) and isYear(period2_end))
						then
							return {period1, period2}
						end
					end
				end
			end
		end
	end
	
	return nil
end

-- takes two Wikidata entities and returns either two strings or nil
-- considers sitelinks and labels in the local langauge and in English
function takeAdjacentNumbersFromEntities(options, entity1, entity2)
	if entity1 and entity2 then
		local adjacentNumbers = nil
		
		local languageCodes = {mw.getContentLanguage():getCode(), 'en'}
		for _, languageCode in pairs(languageCodes) do
			local wikiCode = languageCode .. 'wiki'
			local sitelink1 = entity1:getSitelink(wikiCode)
			local sitelink2 = entity2:getSitelink(wikiCode)
			adjacentNumbers = takeAdjacentNumbersFromStrings(options, sitelink1, sitelink2)
			if adjacentNumbers then
				return adjacentNumbers
			end
		
			local label1 = entity1:getLabel(languageCode) -- TODO: get rid of fallback?
			local label2 = entity2:getLabel(languageCode)
			adjacentNumbers = takeAdjacentNumbersFromStrings(options, label1, label2)
			if adjacentNumbers then
				return adjacentNumbers
			end
		end
	end
	
	return nil
end

function formatAdjacentSnak(context, options, snak)
	local direction = options.direction or ''
	
	if snak and snak.datavalue and snak.datavalue.value and snak.datavalue.value.id then
		local mainText = nil
		
		local adjacentEntity = mw.wikibase.getEntity(snak.datavalue.value.id)
		if direction == 'P155' then -- previous
			local adjacentNumbers = takeAdjacentNumbersFromEntities(options, adjacentEntity, options.entity) -- or reverse order
			if adjacentNumbers then
				mainText = adjacentNumbers[1] -- or 2
			end
		elseif direction == 'P156' then -- next
			local adjacentNumbers = takeAdjacentNumbersFromEntities(options, options.entity, adjacentEntity)
			if adjacentNumbers then
				mainText = adjacentNumbers[2]
			end
		end
		
		-- options should not be changed, as they are reused for P155 and P156
		local optionsCopy = {}
		for k, v in pairs(options) do
			optionsCopy[k] = v
		end
		if (not optionsCopy.text) and mainText then
			local prefix = options.prefix or ''
			local postfix = options.postfix or ''
			optionsCopy.text = prefix .. mainText .. postfix
		end
		
		local link = context.formatSnak(optionsCopy, snak)
		return link
	end
	
	return nil
end

-- function is available from outside, see documentation
function p.formatAdjacentProperty(context, options)
	if (not context) then error('context not specified'); end;
	if (not options) then error('options not specified'); end;
	if (not options.entity) then error('options.entity missing'); end;

	if options.value == '-' then
		return ''
	end

	if options.value then
		return options.value
	end

	local properties = options.property or '' -- TODO: properties vs. property?
	if properties == '' then
		properties = 'Q'
	elseif properties == '-' then
		properties = ''
	end
	local properties = mw.text.split(properties, ', ?')
	
	local direction = options.direction or '' -- TODO: not repeating inside
	
	local formattedClaims = {}

	for _, property in pairs(properties) do
		if property == 'Q' then
			local claims = context.selectClaims(options, direction)
			if claims then
				for _, claim in pairs(claims) do
					if claim.mainsnak then
						local link = formatAdjacentSnak(context, options, claim.mainsnak)
						if link and link ~= '' then
							local formattedClaim = '<span class="wikidata-claim" data-wikidata-property-id="' .. direction .. '" data-wikidata-claim-id="' .. claim.id .. '">' .. link .. '</span>'
							table.insert(formattedClaims, formattedClaim)
						end
					end
				end
			end
		elseif mw.ustring.match(property, '^P%d+$') then
			local claims = context.selectClaims(options, property)
			if claims then
				for _, claim in pairs(claims) do
					if claim.qualifiers and claim.qualifiers[direction] then
						for _, snak in pairs(claim.qualifiers[direction]) do
							local link = formatAdjacentSnak(context, options, snak)
							if link and link ~= '' then
								local formattedClaim = '<span class="wikidata-claim" data-wikidata-property-id="' .. property .. '" data-wikidata-claim-id="' .. claim.id .. '">' .. link .. '</span>'
								table.insert(formattedClaims, formattedClaim)
							end
						end
					end
				end
			end
		end
	end
	
	-- from wikidata.selectClaims
	if options.limit and options.limit ~= '' and options.limit ~= '-'  then
		local limit = tonumber(options.limit, 10);
		while #formattedClaims > limit do
			table.remove(formattedClaims);
		end
	end
	
	-- from wikidata.formatPropertyDefault
	local out = mw.text.listToText(formattedClaims, options.separator, options.conjunction)
	if out ~= '' then
		if options.before then
			out = options.before .. out
		end
		if options.after then
			out = out .. options.after
		end
	end

	return out
end

-- function is available from outside, see documentation
function p.formatChronologyProperty(context, options)
	if (not context) then error('context not specified'); end;
	if (not options) then error('options not specified'); end;
	if (not options.entity) then error('options.entity missing'); end;

	options.value = options.previousLink -- TODO: copy?
	options.separator = '<br>←&nbsp;'
	options.conjunction = '<br>←&nbsp;'
	options.direction = 'P155'
	local previousLink = p.formatAdjacentProperty(context, options)
	
	options.value = options.nextLink
	options.separator = '&nbsp;→<br>'
	options.conjunction = '&nbsp;→<br>'
	options.direction = 'P156'
	local nextLink = p.formatAdjacentProperty(context, options)

	local res = ''
	if previousLink and previousLink ~= '' then
		res = res .. '<div style="width:50%; float: left; text-align: left;">←&nbsp;' .. previousLink .. '</div>'
	end
	if nextLink and nextLink ~= ''  then
		res = res .. '<div style="width:50%; float: right; text-align: right;">' .. nextLink .. '&nbsp;→</div>'
	end
	return res
end

return p