# Module:Adjacent stations

require('strict')  

local p = {}  

local lang = 'ta' -- local default language  

-- Below these comments: Internationalization table  
-- How to translate this module (for languages without variants):  
-- • Characters inside single and double quotation marks are called strings.  
-- The strings in this i18n table are used as output.  
-- • Strings within square brackets are keys.  
-- • Strings are concatenated (joined) with two dots.  
-- • Set the string after «local lang =» to your language's code.  
-- Change the first key after "i18n" (usually "en-GB") to the same thing.  
-- • For each string which is not inside a function, translate it directly.  
-- • Strings with keys named "format" are Lua regular expressions.  
-- «» is a match; «.+» means all characters; «%s+» means all spaces.  
-- • For each string which is concatenated to the variable «var»,  
-- translate the phrase assuming that «var» will be a noun.  
-- • Remove any unnecessary translations.  

local i18n = require("Module:Adjacent stations/i18n")  
local function getData(system, verify)  
if verify then  
local title = mw.title.new('Module:Adjacent stations/' .. system -- .. '/sandbox'  
if not (title and title.exists) then return nil end  
end  
return require('Module:Adjacent stations/' .. system -- .. '/sandbox'  
end  

local function getLine(data, lineN)  
if lineN then  
if data['aliases'] then  
lineN = data['aliases'][mw.ustring.lower(lineN)] or lineN  
end  
local default = data['lines']['_default'] or {}  
local line = data['lines'][lineN] or {}  
for k, v in pairs(default) do  
if v then line[k] = line[k] or v end  
end  
line['title'] = line['title'] and mw.ustring.gsub(line['title'], '%%1', lineN)  
return line, lineN  
end  
end  

local function getColor(data, system, line, Type, )  
if system then  
if line then return :expandTemplate{ title = system .. ' color', args = {line, ['branch'] = Type} } end  
return :expandTemplate{ title = system .. ' color' }  
else  
line = (getLine(data, line))  
local default = data['lines']['_default']  
if line or default then  
default = default or {}  
if not line then line = mw.clone(default) end  
local color = line['color'] or line['background color'] or default['color'] or default['background color'] or data['system color']  
local Type_value = Type and line['types'] and (line['types'][Type] and line['types'][Type]['color'])  
if Type_value then color = Type_value end  
return color  
end  
return (default and (default['color'] or default['background color']) or data['system color'] or '')  
end  
end  

local lineN, typeN  

local function somethingMissing(name, key, formats)  
local formatKeys = {}  
for k in pairs(formats) do  
table.insert(formatKeys, k)  
end  
return name .. ' was "' .. key .. '" but neither an entry for it nor a default was found. Choices were: ' .. table.concat(formatKeys, ', ')  
end  

local function getStation(station, _Format)  
if type(_Format) == 'table' then  
local lineNformats = _Format  
_Format = lineNformats[lineN] or lineNformats[1]  
if not _Format then  
error(somethingMissing('lineN', lineN, lineNformats))  
elseif type(_Format) == 'table' then  
local typeNformats = _Format  
_Format = typeNformats[typeN] or typeNformats[1]  
if not _Format then  
error(somethingMissing('typeN', typeN, typeNformats))  
end  
end  
end  
if typeN then _Format = mw.ustring.gsub(_Format, '%%3', typeN) end  
if lineN then _Format = mw.ustring.gsub(_Format, '%%2', lineN) end  
return (mw.ustring.match(_Format, '%[%[.+%]%]')) and (mw.ustring.gsub(_Format, '%%1', station)) or table.concat({*, station, *})  
end  

local function getTerminusText(var, Format)  
local function subst(var1, var2)  
-- var1 is the terminus or table of termini; var2 is the key for the table of termini  
return type(var1) == 'string' and getStation(var1, (Format[var1] or Format[1]))  
or type(var1) == 'table' and #var1 > 0 and getStation(var1[var2], (Format[var1[var2]] or Format[1]))  
or ''  
end  

if Format then  
if type(var) == 'string' then  
return subst(var)  
elseif type(var) == 'table' and #var > 0 then  
local t = {subst(var, 1)}  

for i = 2, #var - 1 do  
t[i] = i18n[lang]['comma'](subst(var, i))  
end  

if #var > 1 then t[#var] = i18n[lang]['or'](subst(var, #var)) end  
if var['via'] then  
if i18n[lang]['via-first'] then  
table.insert(t, 1, i18n[lang]['via'](subst(var, 'via')))  
else  
table.insert(t, i18n[lang]['via'](subst(var, 'via')))  
end  
end  

return table.concat(t)  
else  
return ''  
end  
else  
return var or ''  
end  
end  

function p._main(_args) -- Arguments are processed here instead of the main function  

local yesno = require('Module:Yesno')  
local trimq = require('Module:Trim quotes')._trim  

local boolean = {  
['oneway-'] = true,  
['oneway-'] = true,  
['reverse'] = true,  
['reverse-'] = true,  
['reverse-'] = true  
}  

local args = {} -- Processed arguments  
local index = {} -- A list of addresses corresponding to number suffixes in the arguments  

for k, v in pairs(_args) do -- Maps each raw argument to processed arguments by string matching  
_args[k] = v:match('^%s*(.-)%s*$')  
if _args[k] and _args[k] ~= '' then  
local a = mw.ustring.match(k, '^(.*%D)%d+$') or k -- The parameter; address 1 can be omitted  
local b = tonumber(mw.ustring.match(k, '^.*%D(%d+)$')) or 1 -- The address for a given argument; address 1 can be omitted  

if boolean[a] then  
v = yesno(v)  
end  

if not args[b] then  
args[b] = {[a] = v}  
table.insert(index, b)  
elseif args[b][a] then  
return error(i18n[lang]['error_duplicate'](a .. b))  
else  
args[b][a] = v  
end  
end  
end  
table.sort(index)  

local function small(s, italic)  
return italic and '<div>' .. s .. '</div>'  
or '<div>' .. s .. '</div>'  
end  

local style = { -- Style for each cell type  
['header cell'] = '|',  
['header midcell'] = '|',  
['body cell'] = '|',  
['body banner'] = 'wikitable adjacent-stations' .. noclearclass .. '"'}  

for i, v in ipairs(index) do  
-- If an address has a system argument, indexes the data module  
data[v] = args[v]['system'] and getData(args[v]['system'])  
-- If an address has no system, the row uses data from the previous address  
or data[index[i - 1]]  
or (args[v]['header'] and getData(args[index[i+1]]['system']))  
or error(i18n[lang]['error_unknown'](args[v]['system']))  

local lang = data[v]['lang'] or lang  

if args[v]['system'] and not args[v]['hide-system'] then -- Header row  
local stop_noun = data[v]['header stop noun'] or i18n[lang]['stop_noun']  
table.insert(wikitable, table.concat({'\n|-',  
'\n! scope="col" ', style['header cell'], i18n[lang]['preceding'](stop_noun),  
'\n! scope="col" ', style['header midcell'], (data[v]['system icon'] and data[v]['system icon'] .. ' ' or ''), (data[v]['system title'] or ('[['.. args[v]['system'] ..']]')),  
'\n! scope="col" ', style['header cell'], i18n[lang]['following'](stop_noun)  
}))  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
end  

if args[v]['header'] then -- Subheader  
table.insert(wikitable, '\n|-\n!|'.. args[v]['header'])  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
end  

if args[v]['line'] or args[v][*] or args[v][*] or args[v]['nonstop'] then  
if not args[v]['line'] and i > 1 and not args[v]['system'] then  
args[v]['line'] = args[index[i - 1]]['line']  
end  

lineN = args[v]['line'] or '_default'  
typeN = args[v]['type']  
if data[v]['aliases'] then  
lineN = data[v]['aliases'][mw.ustring.lower(lineN)] or lineN  
if typeN then typeN = data[v]['aliases'][mw.ustring.lower(typeN)] or typeN end  
end  

-- get the line table  
local line = data[v]['lines'] and (mw.clone(data[v]['lines'][lineN]) or error(i18n[lang]['error_unknown'](args[v]['line']))) or error(i18n[lang]['error_line'])  
local default = data[v]['lines']['_default'] or {}  
line['title'] = line['title'] or default['title'] or ''  
line['title'] = mw.ustring.gsub(line['title'], '%%1', lineN)  

-- cell across row for non-stop service  
if args[v]['nonstop'] then  
table.insert(wikitable,  
table.concat({'\n|-\n| ',  
style['body cell'],  
((args[v]['nonstop'] == 'former') and i18n[lang]['nonstop_past'] or i18n[lang]['nonstop_present'])(p._box({data = data[v], line = lineN, Type = typeN, inline = 'yes'}))  
})  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
else  
local Format = data[v]['station format'] or i18n[lang]['error_format']  

local color, color_2, background_color, circular  
local Type = line['types'] and line['types'][typeN] -- get the line type table  

if Type then  
if Type['color'] then  
-- line color is used as background if there is no background color in the line type table  
background_color = Type['background color'] or line['color']  
color = Type['color']  
color_2 = Type['color2'] or color  
else  
background_color = Type['background color'] or line['background color']  
color = line['color'] or default['color'] or ''  
color_2 = line['color2'] or color  
end  
if Type['circular'] then  
-- Type may override the circular status of the line  
circular = Type['circular']  
end  
else  
background_color = line['background color']  
color = line['color'] or default['color'] or ''  
color_2 = line['color2'] or color  
circular = line['circular']  
end  

-- Alternate termini can be specified based on type  
local sideCell = {true, true}  
for i, b in ipairs({*, *}) do  
if not args[v][b] then -- If no station is given on one side, the station is assumed to be the terminus on that side  
local _through = args[v]['through-' .. b] or args[v]['through']  
local _through_data = getLine(data[v], _through)  
if _through_data then _through = _through_data['title'] or _through end  
sideCell[i] = _through and "*" .. i18n[lang]['through'](trimq(_through)) .. "*"  
or "''" .. trimq((args[v]['reverse-' .. b]  
or args[v]['reverse']) and i18n[lang]['reverse']  
or i18n[lang]['terminus']) .. "''"  
else  
local terminusT  
local terminusN = Type and Type[b .. ' terminus'] or line[b .. ' terminus']  

-- If the terminus table has more than one numbered key or has the via key then the table shows only the default termini, since terminusN[2] cannot be used and terminusN[via] is reserved  
if type(terminusN) == 'string' or (type(terminusN) == 'table' and (terminusN[2] or terminusN['via'])) then  
if args[v]['to-' .. b] then  
terminusT = args[v]['to-' .. b]  
local _or = mw.ustring.match(terminusT, i18n[lang]['or-format'])  
if _or then  
terminusT = mw.ustring.gsub(terminusT, i18n[lang]['or-format'], '\127_OR_\127')  
terminusT = mw.ustring.gsub(terminusT, i18n[lang]['comma-format'], '\127_OR_\127')  
end  
local _via = (mw.ustring.match(terminusT, i18n[lang]['via-format']))  
if _via then  
terminusT = mw.ustring.gsub(terminusT, i18n[lang]['via-format'], '')  
terminusT = mw.text.split(terminusT, '\127_OR_\127')  
terminusT['via'] = _via  
elseif _or then  
terminusT = mw.text.split(terminusT, '\127_OR_\127')  
end  
else  
terminusT = terminusN  
end  
elseif type(terminusN) == 'table' then  
terminusT = terminusN[args[v]['to-' .. b]] or terminusN[args[v]['to']] or terminusN[1]  
end  

local mainText = args[v]['note-' .. b] and getTerminusText(args[v][b], Format) .. small(args[v]['note-' .. b]) or getTerminusText(args[v][b], Format)  

local subText = (args[v]['oneway-' .. b] or line['oneway-' .. b]) and i18n[lang]['oneway']  
or args[v][b] == terminusT and i18n[lang]['terminus']  
or circular and terminusT  
or i18n[lang]['towards'](getTerminusText(terminusT, Format))  
subText = small(subText, true)  

sideCell[i] = mainText .. subText  
end  
end  

table.insert(wikitable, '\n|-')  
table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[1])  
table.insert(wikitable, table.concat({'\n|', style['body banner'], color, '"|',  
'\n|', (background_color and '|' or style['body cell']), line['title'],  

-- Type; table key 'types' in subpages (datatype table, with strings as keys). If table does not exist then the input is displayed as the text  
(typeN and '<div>' .. (Type and Type['title'] or typeN) .. '</div>' or ''),  

-- Note-mid; table key 'note-mid' in subpages. Overridden by user input  
((args[v]['note-mid'] and small(args[v]['note-mid'])) or (Type and Type['note-mid'] and small(Type['note-mid'])) or (line['note-mid'] and small(line['note-mid'])) or ''),  

-- Transfer; uses system's station link table  
(args[v]['transfer'] and small('transfer at ' .. getTerminusText(args[v]['transfer'], Format), true) or ''),  

'\n|', style['body banner'], color_2, '"|'}))  
table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[2])  
end  
end  

if args[v]['note-row'] then -- Note  
if args[v]['note-row']:match('^%s*<tr') or args[v]['note-row']:match('^%s*%|%-') then  
table.insert(wikitable, '\n' .. args[v]['note-row'])  
else  
table.insert(wikitable, '\n|-\n| ' .. style['body cell'] .. args[v]['note-row'])  
end  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
table.insert(wikitable, '')  
end  
end  

local function combine(t, n)  
if t[n + 4] ~= '' and t[n + 4] == t[n] then  
t[n + 4] = '' -- The cell in the next row is deleted  
local rowspan = 2  
while t[n + rowspan * 4] == t[n] do  
t[n + rowspan * 4] = ''  
rowspan = rowspan + 1  
end  
t[n] = mw.ustring.gsub(t[n], '\n|' .. rowspan .. '"legend"><span> </span> ' .. line .. result .. '</div>'  
elseif inline == 'yes' then  
result = '<span>    </span>&nbsp;' .. line .. result  
elseif inline == 'box' then  
result = '<span>    </span>' .. result  
elseif inline == 'link' then  
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')  
if link then  
result = '<span>    </span>' .. result  
else  
result = '<span>    </span>' .. result  
end  
elseif inline == 'square' then  
result = '<span>■</span>&nbsp;' .. line .. result  
elseif inline == 'lsquare' then  
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')  
if link then  
result = '<span>■</span>'  
else  
result = '<span>■</span>'  
end  
elseif inline == 'dot' then  
result = '<span>●</span> ' .. line .. result  
elseif inline == 'ldot' then  
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')  
if link then  
result = '<span>●</span>'  
else  
result = '<span>●</span>'  
end  
elseif inline == 'small' then  
result = '<span> </span>' .. ' ' .. line .. result  
else  
local yesno = require("Module:Yesno")  
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')  
local border_color, text_color  
local color_box = data['color box format'] or data['rail box format'] or {}  
if line_data then  
if line_data['types'] and line_data['types'][typeN] then  
local Type_data = line_data['types'][typeN]  
border_color = Type_data[' color'] or line_data[' color'] or color  
text_color = Type_data['text color'] or line_data['text color']  
if color_box == 'title' and not args[4] then  
lineN = Type_data['short name'] or line_data['short name'] or require('Module:Delink')._delink{line}  
else  
lineN = Type_data['short name'] or line_data['short name'] or lineN  
end  
else  
border_color = line_data[' color'] or color  
text_color = line_data['text color']  
if color_box == 'title' and not args[4] then  
lineN = line_data['short name'] or require('Module:Delink')._delink{line}  
else  
lineN = line_data['short name'] or lineN  
end  
end  
else  
border_color = color  
end  
text_color = text_color and '#' .. text_color or require('Module:Color contrast')._greatercontrast{color}  
local bold = ';font-weight:bold'  
if (yesno(args.bold) == false) then bold = '' end  
if inline == 'route' then -- Template:RouteBox  
if link then  
result = '<span><span>' .. lineN .. '</span></span>'  
else  
result = '<span>' .. lineN .. '</span>'  
end  
elseif inline == 'croute' then -- Template:Bahnlinie  
if link then  
result = '<span><span>' .. lineN .. '</span></span>'  
else  
result = '<span>' .. lineN .. '</span>'  
end  
elseif inline == 'xroute' then -- Template:Bahnlinie  
if link then  
result = '<span><span>' .. lineN .. '</span></span>'  
else  
result = '<span>' .. lineN .. '</span>'  
end  
elseif inline == 'broute' then  
if link then  
result = '<span><span>' .. lineN .. '</span></span>'  
else  
result = '<span>' .. lineN .. '</span>'  
end  
else -- Template:Legend (fallback; duplication to simplify logic)  
result = '<div><span> </span> ' .. line .. result .. '</div>'  
end  
end  

result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')  

return result  
end  
end  

p.box = makeInvokeFunction('_box')  

function p._icon(args, )  
local system = args[1] or args.system  
local data = args.data  

if not system and not data then  
return  
end  

data = data or getData(system)  

local line, line_name = getLine(data, args[2] or args.line)  

local icon  
local icon_format  

if line then  
local line_type = args[3] or args.type  
if line_type then  
line_type = data.aliases and data.aliases[mw.ustring.lower(line_type)] or line_type  
line_type = line.types and line.types[line_type] -- If there's no type table or entry for this type, then it can't have its own icon  
icon_format = line_type['icon format'] or data['type icon format']  

if line_type.icon then  
icon = line_type.icon  
end  
end  

if not icon then  
icon = line.icon  
end  

-- Only if there is no icon use the icon_format.  
if not icon and not icon_format then  
icon_format = line['icon format'] or data['line icon format']  
end  

local default = data.lines._default or {}  
if icon and string.find(icon, "%%1") and default and default.icon then  
icon = mw.ustring.gsub(default.icon, '%%1', line_name)  
end  

end  

if not icon then  
icon = data['system icon']  
end  

if not icon_format then  
icon_format = data['system icon format']  
end  

if icon_format then  
if icon_format ~= 'image' then  
icon = p._box({data = data, [2] = (args[2] or args.line), [3] = icon_format, type = (args[3] or args.type), bold = args.bold, link = args.link}, )  

if args.name then  
if line and line.title then  
return icon .. " " .. line.title  
end  
return icon .. " " .. data["system title"]  
end  
end  
end  

local size = args.size  
if size then  
if mw.ustring.match(size, '%d$') then  
size = '|' .. size .. 'px'  
else  
size = '|' .. size  
end  
-- Upright values are to be disabled until there is use of upright scaling in subpages; doesn't seem to work anyway as of 2018-08-10  
local regex = {  
'|%s*%d*x?%d+px%s*([%]|])', -- '|%s*upright=%d+%.?%d*%s*([%]|])', '|%s*upright%s*([%]|])'  
}  
if mw.ustring.match(icon, regex[1]) then  
icon = mw.ustring.gsub(icon, regex[1], size .. '%1')  
-- elseif mw.ustring.match(icon, regex[2]) then  
-- icon = gsub(icon, regex[2], size .. '%1')  
-- elseif mw.ustring.match(icon, regex[3]) then  
-- icon = gsub(icon, regex[3], size .. '%1')  
else  
icon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1' .. size .. '%2')  
end  
end  

local link = args.link  
if link then  
if mw.ustring.match(icon, '|%s*link=[^%]|]*[%]|]') then  
icon = mw.ustring.gsub(icon, '|%s*link=[^%]|]*([%]|])', '|link=' .. link .. '%1')  
else  
icon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|link=' .. link .. '%2')  
end  
end  

local alt = args.alt or link  
if alt then  
if mw.ustring.match(icon, '|%s*alt=[^%]|]*[%]|]') then  
icon = mw.ustring.gsub(icon, '|%s*alt=[^%]|]*([%]|])', '|alt=' .. alt .. '%1')  
else  
icon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|alt=' .. alt .. '%2')  
end  
end  

if args.name then  
if line and line.title then  
return icon .. " " .. line.title  
end  
return icon .. " " .. data["system title"]  
end  
return icon  
end  

p.icon = makeInvokeFunction('_icon')  
p['rail icon'] = makeTemplateFunction('_icon')  

function p._line(args, )  
local system = args[1] or args.system  
local line = args[2] or args.line  
if not line then return '' end  
local Type = args[3] or args.type  
local data = args.data  
if system or data then  
data = data or getData(system, true)  
if data then  
line = (getLine(data, line)) or error(i18n[lang]['error_unknown'](line))  
if Type then  
Type = data['aliases'] and data['aliases'][mw.ustring.lower(Type)] or Type  
Type = line['types'] and line['types'][Type] and line['types'][Type]['title'] or Type  
end  
line = line['title'] or error(i18n[lang]['error_missing']('title'))  
else  
line = :expandTemplate{ title = system .. ' lines', args = {line, ['branch'] = Type} }  
if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end  
end  

if Type and Type ~= '' then  
if line == '' then  
line = Type  
else  
line = line .. ' – ' .. Type  
end  
end  
return line  
end  
end  

p.line = makeInvokeFunction('_line')  

function p._shortline(args, )  
local system = args[1] or args.system  
lineN = args[2] or args.line  
if not (system or lineN) then return '' end  
local line, Type, line_data  
typeN = args.type  
local data = args.data  
if system or data then  
data = data or getData(system, true)  
if data then  
local default = data['lines']['_default'] or {}  
line, lineN = getLine(data, lineN)  
if typeN then  
typeN = data['aliases'] and data['aliases'][mw.ustring.lower(typeN)] or typeN  
Type = line['types'] and line['types'][typeN] and line['types'][typeN]['title'] or typeN  
end  
line_data = line or error(i18n[lang]['error_unknown'](lineN))  
line = line_data['title'] or default['title'] or error(i18n[lang]['error_missing']('title'))  
line = mw.ustring.gsub(line, '%%1', lineN)  
else  
line = :expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} }  
if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end  
Type = typeN  
end  

local result  

if Type and Type ~= '' then  
if line == '' then  
line = Type  
else  
result = ' – ' .. Type  
end  
end  
if args.note then result = (result or '') .. ' ' .. args.note end  
result = result or ''  

local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')  
if line_data then  
if line_data['types'] and line_data['types'][typeN] then  
local Type_data = line_data['types'][typeN]  
lineN = Type_data['short name'] or line_data['short name'] or lineN  
else  
lineN = line_data['short name'] or lineN  
end  
end  

if link then  
result = * .. lineN .. *  
else  
result = lineN  
end  

result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')  

return result  
end  
end  

p.shortline = makeInvokeFunction('_shortline')  

function p._station(args, )  
local system = args[1] or args.system  
local station = args[2] or args.station  
if not station then return '' end  
lineN = args[3] or args.line  
typeN = args[4] or args.type  
local data = args.data  
if system or data then  
data = data or getData(system, true)  
if data then  
local _Format = data['station format'][station] or data['station format'][1]  
if _Format then  
if data['aliases'] then  
if lineN then  
lineN = data['aliases'][mw.ustring.lower(lineN)] or lineN  
end  
if typeN then  
typeN = data['aliases'][mw.ustring.lower(typeN)] or typeN  
end  
end  
station = getStation(station, _Format)  
else  
station = station or ''  
end  
else  
station = :expandTemplate{ title = system .. ' stations', args = {['station'] = station, ['line'] = lineN, ['branch'] = typeN} }  
end  

return station  
end  
end  

p.station = makeInvokeFunction('_station')  
p['station link'] = makeTemplateFunction('_station')  

function p._terminusTable(args, )  
local system = args[1] or args.system  
lineN = args[2] or args.line  
local side = mw.ustring.sub(mw.ustring.lower(args[3] or args.side or ''), 1, 1)  
typeN = args.type  
local prefix = (side == 'r') and * or *  
local data = args.data  

if system or data then  
data = data or getData(system, true)  
end  
if data then  
local line = getLine(data, lineN) or error(i18n[lang]['error_unknown'](lineN))  
if typeN and data and data['aliases'] then typeN = data['aliases'][mw.ustring.lower(typeN)] or typeN end  
local Type = line['types'] and line['types'][typeN]  

local circular  
if Type then  
if Type['circular'] then  
-- Type may override the circular status of the line  
circular = Type['circular']  
end  
else  
circular = line['circular']  
end  

return Type and Type[prefix .. ' terminus'] or line[prefix .. ' terminus'], data['station format'] or i18n[lang]['error_format'], circular  
else  
local terminus = :expandTemplate{ title = 'S-line/' .. system .. ' ' .. prefix .. '/' .. lineN }  
return mw.ustring.gsub(terminus, '}', typeN)  
end  
end  

function p._terminus(args, )  
local var, Format, circular = p._terminusTable(args, )  

return circular and var or getTerminusText(var, Format)  
end  

p.terminus = makeInvokeFunction('_terminus')  

function p._style(args, )  
local style = args[1] or args.style  
local system = args[2] or args.system  
local line = args[3] or args.line  
local station = args[4] or args.station  
local result = {}  
local data = args.data  
local default = 'background-color:#efefef' -- Default background color for  
if system or data then  
data = data or getData(system, true)  
end  
if data then  
local function getValue(var)  
if type(var) == 'table' then  
var = var[line] or var[1]  
if type(var) == 'table' then  
var = var[station] or var[1]  
end  
end  
if var ~= '' then return var end  
end  

if style == 'header' then  
local tmp = data['name format'] and getValue(data['name format'])  
if tmp then table.insert(result, tmp) end  
elseif style == 'subheader' then  
local tmp = data['header background color'] and getValue(data['header background color'])  
if tmp then  
table.insert(result, 'background-color:#' .. tmp)  
local color = data['header text color'] and getValue(data['header text color'])  
if color then  
table.insert(result, 'color:#' .. color)  
else  
local greatercontrast = require('Module:Color contrast')._greatercontrast  
if greatercontrast{tmp} == '#FFFFFF' then table.insert(result, 'color:#FFFFFF') end  
end  
else  
table.insert(result, default)  
local color = data['header text color'] and getValue(data['header text color'])  
if color then table.insert(result, 'color:#' .. color) end  
end  
end  
result = table.concat(result, ';')  
elseif system then  
local title = 'Template:' .. system .. ' style'  
local titleObj = mw.title.new(title)  
if titleObj and titleObj.exists then  
local tmp  
if style == 'header' then  
tmp = :expandTemplate{ title = title, args = {'name_format', line, station} }  
if tmp ~= '' then table.insert(result, tmp) end  
elseif style == 'subheader' then  
tmp = :expandTemplate{ title = title, args = {'thbgcolor', line, station} }  
if tmp ~= '' then  
table.insert(result, 'background-color:#' .. tmp)  
local color = :expandTemplate{ title = title, args = {'thcolor', line, station} }  
if color ~= '' then  
table.insert(result, 'color:#' .. color)  
else  
local ratio = require('Module:Color contrast')._ratio  
if ratio{tmp, '222222'} < 4.5 then table.insert(result, 'color:#FFFFFF') end -- 222222 is the default text color in Vector  
end  
else  
table.insert(result, default)  
tmp = :expandTemplate{ title = title, args = {'thcolor', line, station} }  
if tmp ~= '' then  
table.insert(result, 'color:#' .. tmp)  
end  
end  
end  
result = table.concat(result, ';')  
else  
if style == 'subheader' then  
result = default  
else  
result = ''  
end  
end  
else  
if style == 'subheader' then  
result = default  
else  
result = ''  
end  
end  

return result  
end  

function p.style  
local args = getArgs(, {frameOnly = true})  
return p._style(args, )  
end  

function p.convert  
local args = .args  
local code = mw.text.split(mw.ustring.gsub(args[1], '^%s*%s*$', '%1'), '%s*}}%s*'  
if not mw.ustring.match(code, '[%[{]') then  
code = mw.ustring.gsub(code, '|[^=|]*=$', '')  
code = mw.ustring.gsub(code, '|[^=|]*$', '')  
end  
end  
if not mw.ustring.match(code, '[%[{]') then  
code = mw.ustring.gsub(code, '|[^=|]*=|', '|')  
code = mw.ustring.gsub(code, '|[^=|]*|', '|')  
code = mw.ustring.gsub(code, '|[^=|]*=\n', '\n')  
code = mw.ustring.gsub(code, '|[^=|]*\n', '\n')  
end  
return t[1]..code..t[2]  
end  

return p