--- 
--- =p._externalData

local p = {}
local lang = mw.getContentLanguage
local english = mw.getLanguage("en")

local function round(x)
	return (math.modf(x + (x < 0 and -0.5 or 0.5)))
end

local function formatChange
	if not previous or previous == 0 then
		return
	end
	if previous == current then
		return "="
	end
	
	local change = current / previous * 100 - 100
	local sign = change < 0 and "−" or "+"
	
	if (math.abs(change)) >= 10 then
		return mw.ustring.format("%s%.0f%%", sign, (math.abs(change)))
	end
	if (math.abs(change)) < 1 then
		return mw.ustring.format("%s%.3f%%", sign, (math.abs(change)))
	end
	return mw.ustring.format("%s%.2f%%", sign, (math.abs(change)))
end

function p._externalData(args)
	local data = mw.ext.data.get(args.datapage)
	
	local dateIndex
	local deathsIndex
	local recoveriesIndex
	local casesIndex
	local class4Index
	local class5Index
	for i, field in ipairs(data.schema.fields) do
		if field.name == "date" or field.name == args.datadate then
			dateIndex = i
		elseif field.name == "deaths" or field.name == args.datadeaths then
			deathsIndex = i
		elseif field.name == "recoveries" or field.name == args.datarecoveries then
			recoveriesIndex = i
		elseif field.name == "cases" or field.name == args.datacases then
			casesIndex = i
		elseif field.name == "class4" or field.name == args.dataclass4 then
			class4Index = i
		elseif field.name == "class5" or field.name == args.dataclass5 then
			class5Index = i
		end
	end
	assert
	assert
	assert
	assert
	assert
	assert
	
	-- Restructure the data as tables with keys.
	local records = {}
	for i, row in ipairs(data.data) do
		local record = {
			date = row[dateIndex],
			deaths = deathsIndex and row[deathsIndex],
			recoveries = recoveriesIndex and row[recoveriesIndex],
			cases = casesIndex and row[casesIndex],
			class4 = class4Index and row[class4Index],
			class5 = class5Index and row[class5Index],
			options = {},
			streak = 1,
		local prevRecord = records[#records] or {}
		if casesIndex and not prevRecord.cases and record.cases and record.cases > 0 then
			record.options.firstright1 = "y"
		end
		if deathsIndex and prevRecord.deaths == 0 and record.deaths and record.deaths > 0 then
			record.options.firstright2 = "y"
		end
		if deathsIndex and (prevRecord.deaths or prevRecord.assumedDeaths) and not record.deaths then
			record.assumedDeaths = prevRecord.deaths or prevRecord.assumedDeaths
		end
		if casesIndex and (prevRecord.cases or prevRecord.assumedCases) and not record.cases then
			record.assumedCases = prevRecord.cases or prevRecord.assumedCases
		end
		if record.deaths == prevRecord.deaths
			and record.recoveries == prevRecord.recoveries
			and record.cases == prevRecord.cases
			and record.class4 == prevRecord.class4
			and record.class5 == prevRecord.class5 then
			record.streak = prevRecord.streak + 1
		end
		table.insert
	end
	
	-- Collapse streaks of identical data.
	for i = #records, 1, -1 do
		local record = records[i]
		if record.streak > 3 then
			for j = i, i - record.streak + 3, -1 do
				table.remove
			end
			i = i - record.streak + 2
			record = records[i]
			record.options.collapse = "y"
			record.options.id = english:formatDate:lower
			record.date = nil
		end
	end
	
	-- Stringify the data.
	local rows = {}
	for i, record in ipairs(records) do
		local prevRecord = records[i - 1] or {}
		local row = {
			record.date or "",
			tostring(record.deaths or record.assumedDeaths or ""),
			tostring(record.recoveries or ""),
			tostring(record.cases or record.assumedCases or ""),
			tostring(record.class4 or ""),
			tostring(record.class5 or ""),
			record.cases and lang:formatNum(record.cases) or "",
			record.cases and formatChange or "",
			record.deaths and lang:formatNum(record.deaths) or "",
			record.deaths and formatChange or "",
		for k, v in pairs(record.options) do
			table.insert(row, string.format)
		end
		table.insert(rows, table.concat)
	end
	return table.concat
end

function p.externalData(frame)
	local args = {}
	for k,v in pairs(frame.args) do
		if (v or '') ~= '' then
			args['data'..k] = v
		end
	end
	return p._externalData(args)
end