local p = {}
local getArgs = require("Module:Arguments").getArgs
local data = mw.loadData("Module:Shindo/data")
local messages = mw.loadData("Module:Shindo/messages")
local makeInvokeFunc = require("Module:MakeInvokeFunc")(p)
local message = require("Module:Message")(messages)
local yn = require("Module:Yesno")

local function getValueOfColor(tbl)
	return (tbl[1] + tbl[2] + tbl[3]) / 3 / 255
end

local function rgbColor(tbl)
	return tbl[1] .. (tbl[2] and ", " .. tbl[2] .. (tbl[3] and ", " .. tbl[3] or "") or "")
end

local function averageColor(tbl)
	local colors = {}
	for k,v in pairs(tbl) do
		colors[k] = v
	end
	local avg = {0, 0, 0}
	for _,color in pairs(colors) do
		avg[1] = avg[1] + color[1]
		avg[2] = avg[2] + color[2]
		avg[3] = avg[3] + color[3]
	end
	avg[1] = math.ceil(avg[1] / #colors) - avg[1] / #colors > 0.5 and math.floor(avg[1] / #colors) or math.ceil(avg[1] / #colors)
	avg[2] = math.ceil(avg[2] / #colors) - avg[2] / #colors > 0.5 and math.floor(avg[2] / #colors) or math.ceil(avg[2] / #colors)
	avg[3] = math.ceil(avg[3] / #colors) - avg[3] / #colors > 0.5 and math.floor(avg[3] / #colors) or math.ceil(avg[3] / #colors)
	return avg
end

local function listIntensitiesFromScale(scale, intensities, labelScale, scale2)
	local out = ''
	if yn(labelScale) then
		out = out .. data[scale].short .. ' '
	end
	local categoriesAreSame = true
	local category = ""
	for k,v in pairs(intensities) do
		if data[scale].ranks[v] == nil then error(message("invalidIntensity", {v, scale})) end
		out = out .. data[scale].ranks[v].label
		if k ~= #intensities then
			if #intensities == 2 then
				out = out .. '–'
			else
				out = out .. '/'
			end
		end
		if category == "" and categoriesAreSame then
			category = data[scale].ranks[v].category or ""
		end
		categoriesAreSame = categoriesAreSame and category ~= "" and (data[scale].ranks[v].category and data[scale].ranks[v].category == category or false)
	end
	if categoriesAreSame and category ~= "" and not scale2 then
		out = out .. " (''" .. category .. "'')"
	end
	return out
end

p._color = function(args)
	local scale = string.lower(args.scale or args[1] or error(message("noScaleShortCode")))
	local intensity = string.upper(args.intensity or args[2] or error(message("noIntensity")))
	if data[scale] == nil then error(message("invalidScale", {scale})) end
	if data[scale].ranks[intensity] == nil then error(message("invalidIntensity", {intensity, scale})) end
	local order = data[scale].ranks[intensity].order
	local color = data[scale].colors[order]
	local colorIntensity = getValueOfColor(color)
	return 'background-color:rgb(' .. rgbColor(color) .. '); color:' .. (colorIntensity < 0.5 and "white" or "black") .. ";"
end

p._formatInWikitable = function(args)
	local scale = string.lower(args.scale or args[1] or error(message("noScaleShortCode")))
	local link = args.link ~= nil and args.link or true
	local labelScale = args.labelScale ~= nil and args.labelScale or true
	local doColor = args.color ~= nil and args.color or true
	local intensity = string.upper(args.intensity or args[2] or error(message("noIntensity")))
	local intensities = mw.text.split(intensity, "/") or { intensity }
	if data[scale] == nil then error(message("invalidScale", {scale})) end
	local colors = {}
	for k,v in pairs(intensities) do
		local order = data[scale].ranks[v].order
		colors[k] = data[scale].colors[order]
	end
	local color = averageColor(colors)
	local colorIntensity = getValueOfColor(color)
	local out = ""
	if yn(args.header or false) then
		out = out .. "! " 
	else
		out = out .. "| "
	end
	out = out .. (args.tagProps ~= nil and args.tagProps or "")
	if (yn(doColor)) then
		out = out .. 'style="background-color:rgba(' .. rgbColor(color) .. '); color:' .. (colorIntensity < 0.5 and "white" or "black") .. ';' .. (args.style or "") .. '" | '
	elseif (args.style) then
		out = out .. 'style="' .. (args.style or "") .. '" | '
	end
	if yn(link) then
		out = out .. '[[' .. data[scale].name .. "#" .. data[scale].id_prefix .. data[scale].ranks[intensity].id .. "|"
	end
	if yn(doColor) then
		out = out .. '<span style=\"color:' .. (colorIntensity < 0.5 and "white" or "black") .. ';">'
	end
	out = out .. listIntensitiesFromScale(scale, intensities, labelScale, false)
	if yn(doColor) then
		out = out .. '</span>'
	end
	if yn(link) then
		out = out .. "]]"
	end
	return out
end

p._formatTag = function(args)
	local scale = string.lower(args.scale or args[1] or error(message("noScaleShortCode")))
	local scale2 = args.scale2 or args[3] or nil
	if scale2 ~= nil then scale2 = string.lower(scale2) end
	local link = args.link ~= nil and args.link or true
	local labelScale = args.labelScale ~= nil and args.labelScale or true
	local doColor = args.color ~= nil and args.color or true
	local intensity = string.upper(args.intensity or args[2] or error(message("noIntensity")))
	local intensities = mw.text.split(intensity, "/") or { intensity }
	local intensity2 = args.intensity2 or args[4] or (scale2 and error(message("noIntensity"))) or nil
	local intensities2 = {}
	if intensity2 ~= nil then
		intensity2 = string.upper(intensity2)
		intensities2 = mw.text.split(intensity2, "/") or { intensity2 }
	end
	if data[scale] == nil then error(message("invalidScale", {scale})) end
	if scale2 and data[scale2] == nil then error(message("invalidScale", {scale2})) end
	local colors = {}
	for k,v in pairs(intensities) do
		local order = data[scale].ranks[v].order
		colors[k] = data[scale].colors[order]
	end
	local color = averageColor(colors)
	local colorIntensity = getValueOfColor(color)
	local out = ''
	if yn(doColor) then
		out = out .. '<' .. (args.tag or "span") .. ' style="background-color:rgba(' .. rgbColor(color) .. '); padding:4px; color:' .. (colorIntensity < 0.5 and "white" or "black") ..  '; '
	elseif args.style then
		out = out .. '<' .. (args.tag or "span") .. ' style="'
	else
		out = out .. '<' .. (args.tag or "span")
	end
	if args.style then
		out = out .. args.style
		out = out .. '" '
	elseif yn(doColor) then
		out = out .. '" '
	end
	out = out .. (args.tagProps ~= nil and args.tagProps or "")
	out = out .. ">"
	if yn(link) then
		out = out .. '[[' .. data[scale].name .. "#" .. data[scale].id_prefix .. data[scale].ranks[intensities[1]].id .. "|"
	end
	if yn(doColor) then
		out = out .. '<span style=\"color:' .. (colorIntensity < 0.5 and "white" or "black") .. ';">'
	end
	out = out .. listIntensitiesFromScale(scale, intensities, labelScale, scale2)
	if yn(doColor) then
		out = out .. '</span>'
	end
	if yn(link) then
		out = out .. "]]"
	end
	if (scale2) then
		out = out .. " ("
		if yn(link) then
			out = out .. '[[' .. data[scale2].name .. "#" .. data[scale2].id_prefix .. data[scale2].ranks[intensities2[1]].id .. "|"
		end
		if yn(doColor) then
			out = out .. '<span style=\"color:' .. (colorIntensity < 0.5 and "white" or "black") .. ';">'
		end
		out = out .. listIntensitiesFromScale(scale2, intensities2, true, true)
		if yn(doColor) then
			out = out .. '</span>'
		end
		if yn(link) then
			out = out .. "]]"
		end
		out = out .. ")"
	end
	out = out .. '</'  .. (args.tag or "span") .. '>'
	return mw.getCurrentFrame():preprocess(out)
end

p._format = function(args)
	if args["format"] == "wikitable" then
		return p.formatInWikitable(args)
	else
		return p.formatTag(args)
	end
end

p._getScaleName = function(args)
	local scale = string.lower(args.scale or args[1] or error(message("noScaleShortCode")))
	if data[scale] == nil then error(message("invalidScale", {scale})) end
	local out = ''
	if yn(args.link or true) then
		out = out .. '[[' .. data[scale].name .. '|'
	end
	out = out .. data[scale].name
	if yn(args.link or true) then
		out = out .. ']]'
	end
	return out
end

-- uses binary search to convert a peak ground acceleration to a seismic intensity
function convert(pga, ranks, ranksSorted, left, right)
	left = left ~= nil and left or 0
	right = right ~= nil and right or #ranksSorted
	index = math.floor((left + right) / 2)
	local lower = ranks[ranksSorted[index + 1]].pga
	local upper = ranksSorted[index + 2] and ranks[ranksSorted[index + 2]].pga or math.huge
	if (pga >= upper) then
		return convert(pga, ranks, ranksSorted, index, right)
	elseif (pga < lower) then
		return convert(pga, ranks, ranksSorted, left, index)
	else
		return ranksSorted[index + 1]
	end
end

p._convert = function(args)
	local scale = string.lower(args.scale or args[1] or error(message("noScaleShortCode")))
	if data[scale] == nil then error(message("invalidScale", {scale})) end
	local ranksSorted = {}
	for k,v in ipairs(data["mmi"].ranksSorted) do ranksSorted[k] = v end
	return convert(tonumber(args.pga or args[2]), data[scale].ranks, ranksSorted)
end

p.convert1 = convert --debugging

p._list = function(args)
	local out = mw.html.create('ul')
	for k,v in pairs(data) do
		local list = out:tag('li')
		list:wikitext('<code>' .. k .. '</code>: [[' .. v.name .. '|' .. v.name .. ']]')
		local tb = list:tag('table'):addClass("wikitable")
		tb
			:tag("tr")
				:tag("th"):wikitext("Seismic intensity"):done()
				:tag("th"):wikitext("Display"):done()
			:done()
		for l,w in pairs(v.order) do
			tb:tag('tr'):wikitext('<td><code>' .. w .. '</code></td> ' .. p.format({k, w, tag = "td"})):done()
		end
		list:done()
	end
	out:allDone()
	return tostring(out)
end

-- make each scale invokable
for k,v in pairs(data) do
	if p["_" .. k] ~= nil then error(message("scaleNameInvalid", k)) end
	p["_" .. k] = function(args)
		args["scale"] = k
		args["intensity"] = args["intensity"] or args[1] or nil
		args["scale2"] = args["scale2"] or args[2] or nil
		args["intensity2"] = args["intensity2"] or args[3] or nil
		if args["format"] == "wikitable" then
			return p.formatInWikitable(args)
		else
			return p.formatTag(args)
		end
	end
end

-- make all functions that begin with _ invokable
local q = mw.clone(p)
for k,v in pairs(q) do
	if mw.ustring.sub(k, 1, 1) == "_" then
		p[mw.ustring.sub(k, 2, #k)] = makeInvokeFunc(k)
	end
end

return p