# Module:Timeline

require('Module:No globals')  

local yesno = require('Module:Yesno')  
local navbox = require('Module:Navbox')._navbox  
local getArgs = require('Module:Arguments').getArgs  
local p = {}  

-- Add a blank table cell  
local function addBlank(args, row, prev, current)  
if row and prev < current then  
if yesno(args.decades) == false then  
row:tag('td')  
:addClass('timeline-blank')  
:cssText(args.blankstyle)  
:attr('colspan', current - prev)  
-- Divide the cell up every decade if showing decades at the top  
else  
local year = prev  

while year < current do  
local dur = math.min(10 - year % 10, current - year)  

row:tag('td')  
:addClass('timeline-blank')  
:cssText(args.blankstyle)  
:attr('colspan', dur)  

year = year + dur  
end  
end  
end  
end  

-- Get timeline entries, start years, and end years  
local function timelineInfo(args)  
local info = {  
startYear = math.huge,  
startYears = {},  
endYear = 0,  
endYears = {},  
entries = {}  
}  

for k, _ in pairs(args) do  
if type(k) == 'string' then  
local num = k:match('^%a+(%d+)$')  

if num then  
table.insert(info.entries, tonumber(num))  
end  
end  
end  

table.sort(info.entries)  

for i, num in ipairs(info.entries) do  
if args['item' .. i] then  
if not args['date' .. i] then  
error('item' .. i .. ' requires a corresponding ' .. 'date' .. i, 0)  
end  

local dates = mw.text.split(args['date' .. i], '-', true)  
local startYear = tonumber(dates[1])  
local endYear = tonumber(dates[2]) or tonumber(os.date('%Y')) + 1  

if not startYear then  
error('date' .. i .. ' contains an invalid timerange', 0)  
end  

info.startYear = math.min(info.startYear, startYear)  
info.endYear = math.max(info.endYear, endYear)  
info.startYears[i] = startYear  
info.endYears[i] = endYear  
end  
end  

if args.startoffset then  
info.startYear = info.startYear - tonumber(args.startoffset)  
end  

if args.startyear then  
info.startYear = math.min(info.startYear, tonumber(args.startyear))  
end  

if args.endoffset then  
info.endYear = info.endYear + tonumber(args.endoffset)  
end  

if args.endyear then  
info.endYear = math.max(info.endYear, tonumber(args.endyear))  
end  

return info  
end  

-- Render the date rows  
local function renderDates(args, tbl, info)  
local showDecades = yesno(args.decades)  
local labelRow = nil  

if args.label then  
labelRow = mw.html.create('th')  
:attr('scope', 'col')  
:addClass('navbox-group timeline-label')  
:cssText(args.labelstyle)  
:attr('rowspan', showDecades ~= false and '2' or '1')  
:wikitext(args.label or '')  
end  

-- Render the decades row  
if showDecades ~= false then  
local decadeRow = tbl:tag('tr')  
local year = info.startYear  

decadeRow:node(labelRow)  

while year < info.endYear do  
local dur = math.min(10 - year % 10, info.endYear - year)  

decadeRow:tag('th')  
:attr('scope', 'col')  
:addClass('timeline-decade')  
:cssText(args.datestyle)  
:cssText(args.decadestyle)  
:attr('colspan', dur)  
:wikitext(math.floor(year / 10) .. '0s')  

year = year + dur  
end  
end  

-- Render the years row  
local yearRow = tbl:tag('tr')  
local width = 100 / (info.endYear - info.startYear)  

if showDecades == false then  
yearRow:node(labelRow)  
end  

for i = info.startYear, info.endYear - 1 do  

yearRow:tag('th')  
:attr('scope', 'col')  
:addClass('timeline-year')  
:cssText(args.datestyle)  
:cssText(args.yearstyle)  
:cssText('width:' .. width .. '%')  
:wikitext(showDecades == false and i or i % 10)  
end  
end  

-- Render the timeline itself  
local function renderTimeline(args, tbl, info)  
local row = nil  
local prev = info.startYear  
local prevItem = nil  
local prevLabel = nil  
local labelSpan = 0  

for i, num in ipairs(info.entries) do  
if args['row' .. i] or row == nil then  
addBlank(args, row, prev, info.endYear)  

row = tbl:tag('tr')  
prev = info.startYear  

if labelSpan <= 0 and args.label then  
labelSpan = tonumber(args['span' .. i]) or 1  

prevLabel = row:tag('th')  
:attr('scope', 'row')  
:attr('rowspan', labelSpan)  
:addClass('navbox-group timeline-label')  
:cssText(args.labelstyle)  
:cssText(args['labelstyle' .. i] or '')  
:wikitext(args['row' .. i])  
end  

labelSpan = labelSpan - 1  
end  

if args['item' .. i] then  
local content = args['item' .. i]  
local startYear = info.startYears[i]  
local endYear = info.endYears[i]  

addBlank(args, row, prev, startYear)  

-- Shrink previous item so new item can start at the start year  
if prevItem and prev > startYear then  
prevItem:attr('colspan', prevItem:getAttr('colspan') - prev + startYear);  
end  

prevItem = row:tag('td')  
:addClass('timeline-item')  
:cssText(args.itemstyle)  
:cssText(args['style' .. i] or '')  
:attr('colspan', endYear - startYear)  
:wikitext(content)  

prev = endYear  
end  
end  

-- Remove any extra rowspan from the label  
if prevLabel and labelSpan > 0 then  
prevLabel:attr('rowspan', prevLabel:getAttr('rowspan') - labelSpan);  
end  

addBlank(args, row, prev, info.endYear)  
end  

function p.main  
local args = getArgs(, {  
removeBlanks = false,  
wrappers = 'Template:Timeline'  
})  
local targs = {  
listpadding = '0'  
}  
-- Arguments to passthrough to navbox  
local passthrough = {  
'name', 'title', 'above', 'below', 'state', 'navbar', '', 1,  
'image', 'imageleft', 'style', 'bodystyle', 'style', 'bodystyle',  
'basestyle', 'titlestyle', 'abovestyle', 'belowstyle', 'imagestyle',  
'imageleftstyle', 'titleclass', 'aboveclass', 'bodyclass',  
'belowclass', 'imageclass'  
}  
local info = timelineInfo(args)  
local tbl = mw.html.create('table'):addClass('timeline-table')  

renderDates(args, tbl, info)  
renderTimeline(args, tbl, info)  

if yesno(args.footer) then  
renderDates(args, tbl, info)  
end  

for _, name in ipairs(passthrough) do  
targs[name] = args[name]  
end  

targs.list1 = :extensionTag{  
name = 'templatestyles',  
args = {  
src = 'Template:Timeline/style.css'  
}  
} .. tostring(tbl)  

return navbox(targs)  
end  

return p