-- vim: set noexpandtab ft=lua ts=4 sw=4:  
local ENABLE_DEBUG = true  
local Cite = require('Module:Cite')
local fb = require('Module:Fallback')  
local p = {} -- module exports
local L = {} -- alias to local functions
-- (so it can be iterated by p in debug mode)
local _linkconfig -- use links from content language Wikipedia
-- or from Wikidata, default to Wikidata
local _contentlang
local usereferences -- array of references to be prefered in the given order
local usetaxa -- array of taxa to be preferred in the given order
local hideranks -- array of ranks to be show or hide from display
local code = false
local visited = {}
-- background colors for each code
local colors = {
[false] = '#d3d3d3',
[13011] = '#d3d3a4', -- ICZN
[693148] = '#9bcd9b', -- ICNafp
[764] = '#a4d3d3', -- ICNCP
[743780] = '#d3a4d3', -- BC/ICNP
[14920640] = '#2f6fab', -- ICVCN
}  
local i18nmessages = require("Module:I18n/taxobox")  
-- readable taxon properties
local P_IMAGE = "P18"
local P_INSTANCE_OF = "P31"
local P_TAXON_RANK = "P105"
local P_IUCN_STATUS = "P141"
local P_TAXON_PARENT = "P171"
local P_SPREAD_MAP = "P181"
local P_TAXON_NAME = "P225"
local P_STATED_IN = "P248"
local P_AUTHOR = "P405"
local P_AUTHOR_ABBR_IPNI = "P428"
local P_ERA_START = "P523"
local P_ERA_END = "P524"
local P_BASINYM= "P566"
local P_TAXON_YEAR = "P574"
local P_END_TIME = "P582"
local P_EX_AUTHOR = "P697"
local P_AUTHOR_ABBR_ZOOLOGY = "P835"
local P_NOMENCLATURE_CODE = "P944"
local P_COMMON_NAME = "P1843"  
-- readable item
local CLADE = 713623
local GENUS = 34740
local SUBGENUS = 3238261
local RED_DATA_LIST = 32059
local MONOTYPIC_TAXON = 310890
local GEOLOGICAL_ERA = 630830
local SYSTEMATICS = 3516404
local RECOMBINATION = 14594740
local EXTINCT = 237350  
local function capitalize(text)
return mw.ustring.gsub(text, "^%l", mw.ustring.upper)
end  
local function mergeTable(a, b)
for _, value in ipairs(b) do
a[#a + 1] = value
end
return a
end
L.mergeTable = mergeTable  
-- credit to http://lua-users.org/wiki/StringInterpolation
local function namedStringFormat(str, vars)
-- Allow replace_vars{str, vars} syntax as well as
-- replace_vars(str, {vars})
if not vars then
vars = str
str = vars[1]
end
return (string.gsub(str, "({([^}]+)})",
function(whole,i)
return vars[i] or whole
end))
end
L.namedStringFormat = namedStringFormat  
local function setLang(contentlang)
_contentlang = contentlang or "en"
end
L.setLang = setLang  
local function getLang
return _contentlang
end
L.getLang = getLang  
local function i18n(str)
local message = i18nmessages[str]
if type(message) == 'string' then
return message
end
return fb._langSwitch(message, getLang)
end
L.i18n = i18n  
-- parse item-ids like argument (like config[references]) which is a space
-- separated list of item numbers like "Q1 Q2 Q3"
local function parseItemIds(itemids)
local items = {}
local priority = 0
if itemids then
for word in string.gmatch(itemids, "%w+") do
priority = priority + 1
item = "Q" .. tonumber(string.sub(word, 2))
items[item] = priority
end
end
items.size = priority
return items
end
L.parseItemIds = parseItemIds  
-- parse config arguments passed by #invode:taxobox. below are all we
-- support currently:
-- - config[lang]: set content language (default: en)
-- - config[count]: maximum count of taxon to be recursively iterated
-- - config[references]: references to be preffered in the given order
-- - config[dryrun]: generate <pre> block instead of expanding template
-- - config[link]: local or wikidata
local function parseConfig(args)
setLang(args["config[lang]"])
local count = tonumber(args["config[count]"]) or 10
if count > 25 then
-- roughly about 100 expensive parser function calls
error(i18n("taxon-count-too-high"))
end
usereferences = parseItemIds(args["config[references]"])
usetaxa = parseItemIds(args["config[usetaxa]"])  
hideranks = {}
local displaypattern = "^display%[([^]]+)%]$"
local qidpattern = "^Q?(%d+)$"
for k, v in pairs(args) do
v = mw.ustring.lower(v)
if string.match(k, displaypattern) then
k = string.gsub(k, displaypattern, "%1")
if string.match(k, qidpattern) then
k = string.gsub(k, qidpattern, "%1")
k = tonumber(k)
end
-- TODO: i18n?
if ({n=true, no=true, ["false"]=true, hide=true})[v] then
hideranks[k] = true
end
end
end  
_linkconfig = "wikidata"
if args["config[link]"] and
mw.ustring.lower(args["config[link]"]) == "sitelink" then
_linkconfig = "sitelink"
end  
return {
["count"] = count,
["lang"] = lang,
["dryrun"] = args["config[dryrun]"],
["link"] = _linkconfig
}
end  
-- check if International Code of Nomenclature for algae, fungi, and plants (ICNafp) applies
local function icnafpApplies
return code == 693148
end
L.icnafpApplies = icnafpApplies  
-- the label of the item if present in the specified language or 'no label'
-- TODO: merge with getLabel
function getItemLabel(item, lang)
local label = i18n('no-label')
if item then
label = item:getLabel(lang or getLang) or label
end
return label
end
L.getItemLabel = getItemLabel  
local function getLabel(id)
if type(id) == "number" then
id = "Q" .. id
end
return getItemLabel(mw.wikibase.getEntity(id))
end
L.getLabel = getLabel  
local function getLink(id, label, format, named)
if type(id) == "number" then
id = "Q" .. id
end
local link = id
if _linkconfig == "sitelink" then
link = mw.wikibase.sitelink(id)
end
label = label or getItemLabel(mw.wikibase.getEntity(id))
format = format or "%s"
if named then
return namedStringFormat{format, link=link, label=label}
else
return string.format(format, link, label)
end
end
L.getLink = getLink  
-- Collect all claims of the given property of the item
-- Returns all claims and their references in tables combined by the claims' rank.
-- result.preferred[target id of claim] = [target id of P248 reference]
-- use only if the data type of the property is item
local function targetIds(item, property)
local claims = {preferred = {}, normal = {}, deprecated = {}}
if item and item.claims and item.claims[property] then
for _,claim in pairs(item.claims[property]) do
if claim.mainsnak.datavalue and claim.mainsnak.datavalue.value then
local valueid = claim.mainsnak.datavalue.value['numeric-id']  
local refids = {}
if claim.references then
for _,ref in pairs(claim.references) do
for prop, refclaim in pairs(ref.snaks[P_STATED_IN] or {}) do
refids[tostring('Q' .. refclaim.datavalue.value['numeric-id'])] = true
end
end
end
claims[claim.rank][valueid] = refids
else
claims[claim.rank]['novalue'] = true -- snaktype not value
end
end
end
return claims
end
L.targetIds = targetIds  
-- Gives the first highest ranked claim and its references.
-- use only if the data type of the property is item
local function targetId(item, property)
local claims = targetIds(item, property)
if next(claims.preferred) then
return claims.preferred
end
if next(claims.normal) then
return claims.normal
end
return claims.deprecated
end
L.targetId = targetId  
-- Collect all claims of the given property of the item
-- Returns a triple of claims, their qualifiers, and their references in tables combined by the claims' rank.
-- Use only if the data type of the property is string
local function targetStrs(item, property)
choosenclaim = {preferred = {}, normal = {}, deprecated = {}}
choosenqualifiers = {preferred = {}, normal = {}, deprecated = {}}
choosenreferences = {preferred = {}, normal = {}, deprecated = {}}
if item and item.claims and item.claims[property] then
for _,claim in pairs(item.claims[property]) do
if claim.mainsnak and claim.mainsnak.datavalue then
index = #choosenclaim[claim.rank] + 1
mw.log(index, claim.mainsnak.datavalue.value)  
local refids = {}
if claim.references then
for _,ref in pairs(claim.references) do
for prop, refclaim in pairs(ref.snaks[P_STATED_IN] or {}) do
refids['Q' .. refclaim.datavalue.value['numeric-id']] = true
mw.log('string ref', refclaim.datavalue.value['numeric-id'])
end
end
end
if claim.mainsnak.datatype == 'monolingualtext' then
choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
else