# Module:Jct

local p = {}  
-- Change to "" upon deployment.  
local moduleSuffix = ""  

local parserModuleName = "Module:Road data/parser" .. moduleSuffix  
local statenameModuleName = "Module:Jct/statename" .. moduleSuffix -- TODO transition  
local cityModuleName = "Module:Jct/city" .. moduleSuffix  

local concat = table.concat  
local insert = table.insert  
local format = mw.ustring.format  
local trim = mw.text.trim  

local parserModule = require(parserModuleName)  
local parser = parserModule.parser  
local util = require("Module:Road data/util")  

-- Shields  
local defaultShieldSize = 24  

local function addContextBanner(route, name, suffix, bannerSpec)  
local bannerModule = 'Module:Road data/banners/' .. string.upper(route.country)  
local shieldfield = name .. 'shield'  
local shield = parser(route, shieldfield)  
if shield == nil then  
-- This route type does not define shield.  
-- Find shield in the default banner table.  
shield = parser(route, 'shield', name, bannerModule)  
if shield and shield ~= '' then  
if suffix == nil then  
suffix = parser(route, 'shield', 'suffix', bannerModule)  
end  
if suffix and suffix ~= '' then  
shield = shield .. " " .. suffix  
end  
shield = shield .. ".svg"  
end  
end  
if shield and shield ~= '' then  
local shieldSize = defaultShieldSize  
-- Add banner plate.  
insert(bannerSpec, {shield, shieldSize})  
end  
end  

local function bannerSpec(banner, bannerSize, bannerSuffix, route)  
local banners = {}  
if type(banner) == "table" then  
local bannerSizeIsNotTable = type(bannerSize) ~= "table"  
for i,filename in ipairs(banner) do  
local bannersize = bannerSizeIsNotTable and bannerSize or bannerSize[i] or defaultShieldSize  
insert(banners, {filename, bannersize})  
end  
elseif banner ~= '' then  
insert(banners, {banner, bannerSize})  
end  

if route.dir then  
addContextBanner(route, 'dir', bannerSuffix, banners)  
end  
if route.to then  
addContextBanner(route, 'to', bannerSuffix, banners)  
end  

return banners  
end  

local function shieldSpec(route, mainShield, shieldList)  
local shieldSpec = {}  

local shield  
local shieldto = parser(route, 'shieldto')  

if route.to then  
if not shield then shield = shieldto or parser(route, 'shield') or '' end  
else  
if not shield then shield = parser(route, 'shield') or '' end  
end  
if shield == '' then return shieldSpec end  
local orientation = parser(route, 'orientation')  

local function size(route)  
if orientation == "upright" then  
return defaultShieldSize  
else return "x" .. defaultShieldSize  
end  
end  

local shieldsize = size(route)  

local banner = parser(route, 'banner') or {}  
local bannersize = defaultShieldSize  
local bannersuffix = parser(route, 'bannersuffix')  

local bannerIsNotTable = type(banner) ~= "table"  
local bannersizeIsNotTable = type(bannersize) ~= "table"  
local bannersuffixIsNotTable = type(bannersuffix) ~= "table"  

if type(shield) == "table" then  
for i,filename in ipairs(shield) do  
local size = shieldsize or shieldsize[i]  
if size == "" then size = nil end  
-- banner.all describes banners that apply to all multiple shields.  
local shieldBanner = bannerIsNotTable and banner or (banner[i] or banner.all or {})  
-- Banner size is default if the corresponding entry  
-- in bannerSize table is not set.  
local shieldBannerSize =  
bannersizeIsNotTable and bannersize  
or (bannersize[i] or bannersize.all or defaultShieldSize)  
local shieldBannerSuffix = bannersuffix and (bannersuffixIsNotTable and bannersuffix or bannersuffix[i])  
insert(shieldSpec, {  
shield = {filename, size},  
banners = bannerSpec(shieldBanner, shieldBannerSize, shieldBannerSuffix, route)  
})  
end  
elseif shield ~= '' then  
if shieldsize == "" then shieldsize = nil end  
insert(shieldSpec, {  
shield = {shield, shieldsize},  
banners = bannerSpec(banner, bannersize, bannersuffix, route)  
})  
end  

return shieldSpec  
end  

local missingShields  

local shieldExistsCache = {}  

local function render(shieldEntry, scale, showLink)  
local shield = shieldEntry.shield  
local banners = shieldEntry.banners  

local size  
if shield[2] then  
local width, height = mw.ustring.match(shield[2], "(%d*)x?(%d*)")  
width = tonumber(width)  
height = tonumber(height)  
local sizeparts = {}  
if width then  
insert(sizeparts, format("%d", width * scale))  
end  
if height then  
insert(sizeparts, format("x%d", height * scale))  
end  
size = concat(sizeparts)  
else  
size = format("%s%d", landscape and "x" or "", defaultShieldSize * scale)  
end  
local shieldCode = format("", shield[1], size)  
if not banners[1] then return shieldCode end  

for _,banner in ipairs(banners) do  
shieldCode = format("  
%s",  
banner[1],  
defaultShieldSize,  
shieldCode)  
end  
return '<span>' .. shieldCode .. '</span>'  
end  

function p.shield(route, scale, showLink, mainShield, shieldList)  
missingShields = {}  

scale = scale or 1  

local rendered = {}  
for _,entry in ipairs(shieldSpec(route, mainShield, shieldList)) do  
insert(rendered, render(entry, scale, showLink))  
end  
return concat(rendered), missingShields  
end  

-- Links/abbreviations  
function p.link(route)  
local nolink = route.nolink  
local abbr = parser(route, 'abbr')  
if nolink then  
return abbr  
else  
local link = parser(route, 'link')  
if not link or link == '' then  
return abbr  
else  
return format("%s", link, abbr)  
end  
end  
end  
---

-- Links/abbreviations  
local function routeText(route, jctname, )  
local link  
local type = route.type  
if not type or type == '' then  
link = route.route  
else  
link = p.link(route)  
end  

local dir = route.dir and ' ' .. string.lower(route.dir) or ''  
local routeText = link .. dir  

local name = route.name  
if name and name ~= '' then  
local mainText = jctname and name or routeText  
local parenText = jctname and routeText or name  

return format('%s (%s)', mainText, parenText)  
else  
return routeText  
end  
end  

local function extra(args)  
local extraTypes = mw.loadData('Module:Road data/extra')  
local extraIcon = extraTypes[string.lower(args.extra or '')]  
if not extraIcon then return '' end  
local size = defaultShieldSize .. 'px'  
local countryIcon = extraIcon[args.country] or extraIcon.default  
if type(countryIcon) == 'table' then  
local localIcon = countryIcon[args.state] or countryIcon.default  
return string.format("", localIcon, size)  
else  
return string.format("", countryIcon, size)  
end  
end  

local function parseArgs(args)  
local state = args.state or args.province or ''  
args.state = state  
local country  
if args.country and args.country ~= '' then  
country = string.upper(args.country)  
else  
local countryModule = mw.loadData("Module:Road data/countrymask")  
country = countryModule[state] or 'UNK'  
end  
args.country = country  

local params = {'denom', 'county', 'township', 'dab', 'nolink', 'noshield', 'to', 'dir', 'name'}  
local routes = {}  
local routeCount = 1  
local seenTo = false  
while true do  
local routeType = args[routeCount * 2 - 1]  
if not routeType then break end  
local route = {type = routeType, route = args[routeCount * 2]}  
for _,v in pairs(params) do  
route[v] = args[v .. routeCount]  
end  

if args.nolink then  
route.nolink = args.nolink  
end  

route.country = country  
route.state = state  

-- Set the first .to to true.  
-- Set all following .to to ''.  
if seenTo then  
if route.to then  
-- Report duplicate to flag.  
route.toerror = true  
end  
route.to = ''  
elseif route.to then  
route.to = true  
seenTo = true  
end  

insert(routes, route)  
routeCount = routeCount + 1  
end  
return routes  
end  

local function prefix(to, num)  
if to and to ~= '' then  
return num == 1 and 'To ' or ' to '  
end  
return num == 1 and '' or '&nbsp;/ '  
end  

local function addErrorMsg(catCode, msg, errorMsg)  
errorMsg.code = errorMsg.code or catCode  
insert(errorMsg, format('<span>Module:Jct %s</span>', msg))  
end  

function p._jct(args, )  
local routes = parseArgs(args)  
local shields = {}  
local links = {}  
local allMissingShields = {}  
local typeErr = false  
local toErr = false  
= or mw.getCurrentFrame  
for num,route in ipairs(routes) do  
if not (args.noshield or route.noshield) then  
local shield, missingShields = p.shield(route)  
insert(shields, shield)  
if missingShields[1] then  
insert(allMissingShields, concat(missingShields, ' / '))  
end  
end  
local prefix = prefix(route.to, num)  
if prefix ~= '' then insert(links, prefix) end  
insert(links, routeText(route, args.jctname, ))  
typeErr = typeErr or route.typeerror or false  
toErr = toErr or route.toerror or false  
end  
local graphics = concat(shields) .. extra(args) .. ' '  
local linkText = concat(links)  
local cities = ''  
if args.city1 or args.location1 then  
local citiesPrefix  
if args.citiesprefix then  
citiesPrefix = args.citiesprefix ~= * and format(" %s ", args.citiesprefix) or *  
else  
citiesPrefix = '&nbsp;'  
end  
local cityModule = require(cityModuleName)  
cities = citiesPrefix .. cityModule.city(args)  
end  

local errorMsg = {}  
-- Errors must be reported by the level of severity, most severe first.  
if typeErr then  
-- Report invalid type errors.  
addErrorMsg("§", 'error: Invalid route type', errorMsg)  
end  
if #allMissingShields > 0 then  
-- Report missing shield error.  
-- shieldExists would have populated missingShields if shields are missing.  
addErrorMsg("¶", 'error: Missing route marker graphics: ' .. concat(allMissingShields, ' / '), errorMsg)  
end  
if toErr then  
-- Report invalid to errors.  
addErrorMsg("&", 'error: Invalid "to" argument', errorMsg)  
end  
if args.road then  
-- Report deprecated "road" warning.  
addErrorMsg("∆", 'warning: "road" parameter is deprecated', errorMsg)  
end  
if args.rdt then  
-- Report deprecated "rdt" warning.  
addErrorMsg("£", 'warning: "rdt" parameter is deprecated', errorMsg)  
end  
if #errorMsg > 0 then  
local page = mw.title.getCurrentTitle.prefixedText -- Get transcluding page's title  
-- Add a category for the first, most severe error.  
insert(errorMsg, format('', errorMsg.code, page))  
errorMsg = concat(errorMsg)  
else  
errorMsg = ''  
end  

return graphics .. linkText .. cities .. errorMsg  
end  

function p.jct  
-- Import module function to work with passed arguments  
local getArgs = require('Module:Arguments').getArgs  
local args = getArgs(, {removeBlanks = false})  
return p._jct(args, )  
end  

function p._roadlink(args, )  
local routes = parseArgs(args)  
local links = {}  
local typeErr = false  
local toErr = false  
= or mw.getCurrentFrame  
for num,route in ipairs(routes) do  
local prefix = prefix(route.to, num)  
if prefix ~= '' then insert(links, prefix) end  
insert(links, routeText(route, args.jctname, ))  
typeErr = typeErr or route.typeerror or false  
toErr = toErr or route.toerror or false  
end  
local linkText = concat(links)  
local cities = ''  
if args.city1 or args.location1 then  
local citiesPrefix  
if args.citiesprefix then  
citiesPrefix = args.citiesprefix ~= * and format(" %s ", args.citiesprefix) or *  
else  
citiesPrefix = '&nbsp;'  
end  
local cityModule = require(cityModuleName)  
cities = citiesPrefix .. cityModule.city(args)  
end  

local errorMsg = {}  
-- Errors must be reported by the level of severity, most severe first.  
if typeErr then  
-- Report invalid type errors.  
addErrorMsg("2", 'error: Invalid route type', errorMsg)  
end  
if toErr then  
-- Report invalid to errors.  
addErrorMsg("3", 'error: Invalid "to" argument', errorMsg)  
end  
if args.road then  
-- Report deprecated "road" warning.  
addErrorMsg("W", 'warning: "road" parameter is deprecated', errorMsg)  
end  
if #errorMsg > 0 then  
local page = mw.title.getCurrentTitle.prefixedText -- Get transcluding page's title  
-- Add a category for the first, most severe error.  
insert(errorMsg, format('', errorMsg.code, page))  
errorMsg = concat(errorMsg)  
else  
errorMsg = ''  
end  

return linkText .. cities  
end  

function p.roadlink  
-- Import module function to work with passed arguments  
local getArgs = require('Module:Arguments').getArgs  
local args = getArgs(, {removeBlanks = true})  
return p._roadlink(args, )  
end  

return p