require 'Амодуль:No globals'

local p = {}

local lib = require 'Амодуль:Wikidata/lib'

local function in_array(value, array)
	local TableTools = require 'Амодуль:TableTools'
	return TableTools.inArray(array, value)
end

local function checkLimit(array, limit)
	local limit = limit and tonumber(limit)
	if limit then
		return #array >= limit
	end
	return true
end

local function applyLimit(array, limit)
	local limit = limit and tonumber(limit)
	while limit and #array > limit do
		table.remove(array)
	end
end

local function isInLanguage(snak, langs)
	local langs = lib.textToTable(langs)
	if snak.datatype ~= 'monolingualtext' then
		error(lib.raiseInvalidDatatype('isInLanguage', snak.datatype, 'monolingualtext'))
	else
		return lib.IsSnakValue(snak) and in_array(snak.datavalue.value.language, langs)
	end
end

local function hasSnaktype(snak, somevalue, novalue)
	local snaktype = snak.snaktype
	if snaktype == 'somevalue' then
		return somevalue or false
	elseif snaktype == 'novalue' then
		return novalue or false
	end
	return lib.IsSnakValue(snak)
end

local function hasTarget(snak, target)
	local Formatters = require 'Амодуль:Wikidata/Formatters'
	return tostring(Formatters.getRawValue(snak)) == tostring(target)
end

local function hasQualifier(statement, prop, value)
	if statement.qualifiers then
		prop = prop:upper()
		for _, snak in ipairs(statement.qualifiers[prop] or {}) do
			if not value or hasTarget(snak, value) then
				return true
			end
		end
	end
	return false
end

local function withoutQualifier(statement, prop, value)
	return not hasQualifier(statement, prop, value)
end

local function hasRanks(statement, ranks)
	return in_array(statement.rank, ranks)
end

local function hasReferences(statement, options)
	if statement.references then
		if #p.filterReferences(statement.references, options) > 0 then
			return true
		end
	end
	return false
end

local function hasLabel(snak)
	local datatype = snak.datatype
	if datatype ~= 'wikibase-item' and datatype ~= 'wikibase-property' then
		error(lib.raiseInvalidDatatype('hasLabel', datatype, { 'wikibase-item', 'wikibase-property' }))
	end
	if lib.IsSnakValue(snak) then
		local i18n = mw.loadData('Амодуль:Wikidata/i18n')
		local langs = mw.language.getFallbacksFor(i18n.lang)
		table.insert(langs, 1, i18n.lang)
		local Formatters = require 'Амодуль:Wikidata/Formatters'
		if lib.getLabelInLanguage(Formatters.getRawValue(snak), langs) then
			return true
		end
	end
	return false
end

local function hasSitelink(statement)
	local datatype = statement.mainsnak.datatype
	if datatype ~= 'wikibase-item' then
		error(lib.raiseInvalidDatatype('hasSitelink', datatype, { 'wikibase-item' }))
	end
	if lib.IsSnakValue(statement.mainsnak) then
		if mw.wikibase.sitelink(Formatters.getRawValue(statement.mainsnak)) then
			return true
		end
	end
	return false
end

local function isInstance(snak, instance)
	local datatype = snak.datatype
	if datatype ~= 'wikibase-item' and datatype ~= 'wikibase-property' then
		error(lib.raiseInvalidDatatype('isInstance', datatype, { 'wikibase-item', 'wikibase-property' }))
	end
	if lib.IsSnakValue(snak) then
		local Formatters = require 'Амодуль:Wikidata/Formatters'
		local item = Formatters.getRawValue(snak)
		if mw.wikibase.getReferencedEntityId(item, 'P279', lib.textToTable(instance)) then
			return true
		end
	end
	return false
end

local function hasUnit(statement, unit)
	local datatype = statement.mainsnak.datatype
	if datatype ~= 'quantity' then
		error(lib.raiseInvalidDatatype('hasUnit', datatype, { 'quantity' }))
	end
	if lib.IsSnakValue(statement.mainsnak) then
		return (lib.getItemIdFromURI(statement.mainsnak.datavalue.value.unit) or 'Q199') == unit
	else
		return false
	end
end

local function filter(array, callback, ...)
	local i = #array
	while i > 0 do
		if not callback(array[i], ...) then
			table.remove(array, i)
		end
		i = i - 1
	end
end

local function filterMainsnak(statements, callback, ...)
	filter(statements, function (statement, ...)
		return callback(statement.mainsnak, ...)
	end, ...)
end

local function getValuesFromQualifiers(qualifiers)
	local Values = {}
	local Formatters = require 'Амодуль:Wikidata/Formatters'
	for key, array in pairs(lib.props) do
		for _, prop in ipairs(array) do
			for _, snak in ipairs(qualifiers[prop] or {}) do
				if lib.IsSnakValue(snak) then
					Values[key] = Formatters.getRawValue(snak, {})
					break
				end
			end
		end
	end
	return Values
end

function p.filterStatementsFromEntity(entity, options)
	if not options.property or options.property == '' then
		error(lib.formatError('param-not-provided', 'property'))
	end
	if not entity then
		return {}
	end

	local statements = mw.clone(entity:getAllStatements(options.property))
	if #statements ~= 0 then
		p.filterStatements(statements, options)
	end
	return statements
end

function p.filterStatements(statements, options)
	-- apply filter by rank
	local rank = options.rank or "valid"
	if rank ~= "all" then
		if rank == "valid" then
			filter(statements, hasRanks, { "normal", "preferred" })
		elseif rank == "best" then
			local bestRank = "normal"
			for _, statement in ipairs(statements) do
				if statement.rank == "preferred" then
					bestRank = "preferred"
					break
				end
			end
			filter(statements, hasRanks, { bestRank })
		else
			filter(statements, hasRanks, { rank })
		end
		if #statements == 0 then return end
	end
	-- apply filter by source
	if options.ref then
		filter(statements, hasReferences, options)
		if #statements == 0 then return end
	end
	-- apply filter by snak type
	filterMainsnak(statements, hasSnaktype, options.somevalue and true, options.novalue and true)
	if #statements == 0 then return end
	-- apply filter by target value
	if options.withtarget then
		filterMainsnak(statements, hasTarget, options.withtarget)
		if #statements == 0 then return end
	end
	-- apply filter by qualifier property
	if options.withqualifier then
		filter(statements, hasQualifier, options.withqualifier, options.withqualifiervalue)
		if #statements == 0 then return end
	end
	if options.withoutqualifier then
		filter(statements, withoutQualifier, options.withoutqualifier, options.withoutqualifiervalue)
		if #statements == 0 then return end
	end
	-- apply filter by language
	if options.withlang then
		filterMainsnak(statements, isInLanguage, options.withlang)
		if #statements == 0 then return end
	end
	-- apply filter by unit
	if options.withunit then
		filter(statements, hasUnit, options.withunit)
		if #statements == 0 then return end
	end
	-- apply filter by time
	if options.date then
		local date
		local Time = require 'Амодуль:Time'
		if type(options.date) == 'table' then
			date = options.date
		elseif options.date == '#now' then
			local osDate = os.date('!*t')
			date = Time.new{
				year = osDate.year,
				month = osDate.month,
				day = osDate.day,
				precision = Time.PRECISION.DAY,
				calendar = Time.CALENDAR.GREGORIAN,
			}
		else
			date = Time.newFromIso8601(options.date)
		end
		if not date then
			error(lib.formatError('invalid-date', tostring(options.date)))
		end

		local oldStatements = mw.clone(statements)
		while #statements > 0 do table.remove(statements) end
		for _, statement in ipairs(oldStatements) do
			local Values = getValuesFromQualifiers(statement.qualifiers or {})
			if Values.point then
				if date == Values.point then
					filter(statements, function(st)
						local val = getValuesFromQualifiers(st.qualifiers).point
						if val then
							return val == Values.point
						end
						return true
					end)
					table.insert(statements, statement)
				elseif Values.point < date then
					if #statements == 0 then
						table.insert(statements, statement)
					else
						local same, ins
						for _, st in ipairs(statements) do
							local val = getValuesFromQualifiers(st.qualifiers).point
							if val then
								if date == Values.point then
									same = true
									break
								end
								if val == Values.point or val < Values.point then
									ins = true
								end
							end
						end
						if ins and not same then
							filter(statements, function(st)
								local val = getValuesFromQualifiers(st.qualifiers).point
								return not val or val == Values.point
							end)
							table.insert(statements, statement)
						end
					end
				end
			else
				if Values.begin then
					if Values.begin < date then
						if not Values.ending then
							table.insert(statements, statement)
						elseif date < Values.ending then
							table.insert(statements, statement)
						end
					end
				elseif Values.ending then
					if date < Values.ending then
						if not Values.begin then
							table.insert(statements, statement)
						elseif Values.begin < date then
							table.insert(statements, statement)
						end
					end
				end
			end
		end
		if #statements == 0 then return end
	end
	if lib.IsOptionTrue(options, 'withlabel') then
		filterMainsnak(statements, hasLabel)
		if #statements == 0 then return end
	end
	if lib.IsOptionTrue(options, 'withsitelink') then
		filter(statements, hasSitelink)
		if #statements == 0 then return end
	end
	-- apply filter by class
	if options.instance then
		filterMainsnak(statements, isInstance, options.instance)
		if #statements == 0 then return end
	end
	-- sort statements if needed
	if options.sort then -- patří to sem?
		local Sorters = require 'Амодуль:Wikidata/Sorters'
		Sorters.sortStatements(statements, options)
	end
	-- apply filter by limit
	applyLimit(statements, options.limit)
end

function p.filterQualifiers(qualifiers, options)
	filter(qualifiers, hasSnaktype, options.somevalue and true, options.novalue and true)
	if #qualifiers == 0 then return end
	if options.withlang then
		filter(qualifiers, isInLanguage, options.withlang)
		if #qualifiers == 0 then return end
	end
	if lib.IsOptionTrue(options, 'withlabel') then
		filter(qualifiers, hasLabel)
		if #qualifiers == 0 then return end
	end
	if options.instance then
		filter(qualifiers, isInstance, options.instance)
		if #qualifiers == 0 then return end
	end
	if options.sort then
		local Sorters = require 'Амодуль:Wikidata/Sorters'
		Sorters.sortQualifiers(qualifiers, options)
	end
	applyLimit(qualifiers, options.limit)
end

function p.filterReferences(references, options)
	if options.ref == '#any' then
		-- @deprecated
		return references
	end

	local oldReferences, References = references, {}
	if options.ref == 'valid' then
		local map = (require 'Амодуль:Wikidata/cite').props
		for _, ref in ipairs(oldReferences) do
			for _, props in pairs(map) do
				for _, prop in ipairs(props) do
					if ref.snaks[prop] then
						table.insert(References, ref)
					end
				end
			end
		end
	end

	if options.min_ref and not checkLimit(References, options.min_ref) then
		return {}
	end
	-- @deprecated
	return References
end

return p