Documentation for this module may be created at Module:Calendar/doc

require('strict')

local p = {}
local root = {}

local function addRow(row)
	table.insert(root, row)
end

local function isLeapYear(year)
    return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end

local function getDaysInMonth(month, year)
    return month == 2 and isLeapYear(year) and 29
           or ("\31\28\31\30\31\30\31\31\30\31\30\31"):byte(month)
end

local function getMonthNumber(m)
    if tonumber(m) ~= nil then
      return tonumber(m)
    elseif m == "current" then
      return tonumber(os.date("%m"))
    elseif m == "next" then
      return tonumber(os.date("%m")) % 12 + 1
    elseif m == "last" then
      return (tonumber(os.date("%m")) - 2) % 12 + 1
    else
      return 1
    end
end

local function applyDefaultValue(inputValue, defaultValue)
	return (inputValue and inputValue ~= "") and inputValue or defaultValue
end

local function getWeekdays(format)
	if format == "Mon1st" then
		return {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
	elseif format == "iso" then
		return {"Wk", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
	else
		return {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
	end
end

local function getIsoWeekNumber(year, month, day)
	local daysFromStart = os.difftime(os.time{year= year, month=month, day=day}, os.time{year=year, month=1, day=1}) / 86400
	local daysUntilEnd = os.difftime(os.time{year= year + 1, month=1, day=1}, os.time{year=year, month=month, day=day}) / 86400
	local firstWeekday = tonumber(os.date("%w",os.time{year=year, month=month, day=1}))
	local nextFirstWeekday = tonumber(os.date("%w",os.time{year=year+1, month=month, day=1}))
	
	if daysUntilEnd < 4 and nextFirstWeekday >= 4 then return 1 end
	local daysFromFirstMonday = daysFromStart	- (8 - firstWeekday)
	local iso = math.floor(daysFromFirstMonday / 7) + 2 - (firstWeekday > 5 and 1 or 0)
	return iso == 0 and getIsoWeekNumber(year - 1, 12, 26) or iso
end

local function make_link(text, flag, pref, suff)
	return flag and "[[" .. pref .. text .. suff .. "|" .. text .. "]]" or text
end

local function format_date(date)
	return date < 10 and "&nbsp;" .. date or date
end

local function _calendar(frame, _monthNumber, full_mode, float)
	local monthNumber = _monthNumber
	
	local year = tonumber(applyDefaultValue(frame.args.year,os.date("%Y")))
	local month = os.date("%B", os.time{year=year, month=monthNumber, day=1})
	local show_year = applyDefaultValue(frame.args.show_year,"on")
	local format = applyDefaultValue(frame.args.format,"Sun1st") --format = "iso"
	local colspan = (format == "iso" and 8 or 7)
	local prevnext = applyDefaultValue(frame.args.prevnext == "on", false)
	local end_note = applyDefaultValue(frame.args.EndNote, "")
	local colour = frame.args.colour
	local title_colour = applyDefaultValue(colour,frame.args.title_colour)
	local week_colour = applyDefaultValue(colour,frame.args.week_colour)
	local wknum_colour = frame.args.wknum_colour
	local lk = applyDefaultValue(frame.args.lk, "")
	local show_link_year = string.match(lk, "y")
	local show_link_month = string.match(lk, "m")
	local show_link_day = string.match(lk, "d")
	local lk_pref = frame.args.lk_pref
	local lk_suff = frame.args.lk_suff
	local lk_pref_d = applyDefaultValue(lk_pref,frame.args.lk_pref_d)
	local lk_suff_d = applyDefaultValue(lk_suff,frame.args.lk_suff_d)
	local lk_pref_m = applyDefaultValue(lk_pref,frame.args.lk_pref_m)
	local lk_suff_m = applyDefaultValue(lk_suff,frame.args.lk_suff_m)
	local lk_pref_mnext = frame.args.lk_pref_mnext
	local lk_pref_mprev = frame.args.lk_pref_mprev
	local lk_suff_mnext = frame.args.lk_suff_mnext
	local lk_suff_mprev = frame.args.lk_suff_mprev
	if full_mode and show_year == "on" then show_year = "with month" end 
	
	-- calculate helper variables
	local prevMonth = os.date("%B", os.time{year=year, month=(monthNumber - 2) % 12 + 1, day=1})
	local nextMonth = os.date("%B", os.time{year=year, month=monthNumber % 12 + 1, day=1})
	local firstWeekday = tonumber(os.date("%w",os.time{year=year, month=monthNumber, day=1}))
	local daysInMonth = getDaysInMonth(monthNumber, year)

	-- constuct title
	addRow("{| class=\"toccolours " .. float ..  "\" style=\"text-align:center;\" cellpadding=2 cellspacing=0\n")
		
	addRow("|- class=\"navbox-title\"" .. " style=\"background:" .. title_colour .. ";\"\n")
		
	local title_colspan = (prevnext and colspan - 2 or colspan)
	local title = make_link(month .. (show_year == "with month" and " " .. year or ""), show_link_month, lk_pref_m, lk_suff_m)
	local title_row = "|colspan=\"" .. title_colspan .. "\"|'''" .. title .. "'''\n"
	
	if prevnext then
		addRow("|[[" .. lk_pref_mprev .. prevMonth .. lk_suff_mprev .. "|  << ]]\n" .. title_row .. "|[[" .. lk_pref_mnext .. nextMonth .. lk_suff_mnext .. "|  >> ]]\n")
	else 
		addRow(title_row)
	end

	-- construct weekdays
	addRow("|- class=\"navbox-title\"" .. " style=\"background:" .. week_colour .. ";\"\n")
	for i, wd in ipairs(getWeekdays(format)) do
    	addRow("|'''" .. wd .. "'''\n")
	end
	addRow("|-\n")

	-- construct dates
	local daysToMiss = firstWeekday - ((format == "Mon1st" or format == "iso") and 1 or 0)
	local date = - daysToMiss + 1
	local num_rows = full_mode and 6 or math.ceil((daysInMonth + daysToMiss) / 7)
	
	for i = 1, num_rows * colspan do
		local lastColumn = i % colspan == 0
		local firstColumn = i % colspan == 1
		
		if format == "iso" and firstColumn then
			addRow("| class=\"navbox-abovebelow\" style=\"background:" .. wknum_colour .. ";\"|" .. getIsoWeekNumber(year, monthNumber, date) .. "\n")
		elseif date > 0 and date <= daysInMonth then
			addRow("|" .. make_link(format_date(date), show_link_day, lk_pref_d .. month .. " ", lk_suff_d) .. "\n")
			date = date + 1
		else 
			addRow("|&nbsp;\n") 
			date = date + 1
		end
		
		if(lastColumn) then addRow("|-\n") end
	end
	
	if end_note ~= "" then
		addRow("|- class=\"navbox-title\" \n")
		addRow("|colspan=\"" .. colspan .."\"|" .. end_note .. "\n")
	end
	
	if show_year == "on" then
		addRow("|- class=\"navbox-title\"" .. " style=\"background:" .. title_colour .. ";\"\n")
		addRow("|colspan=\"" .. colspan .. "\"|'''" .. make_link(year, show_link_year, "", "") .. "'''\n")
	end
	addRow("|}")
end

function p.main(frame)

	local _month = frame.args.month
	local full_calendar = not (_month and _month ~= "")
	local year = tonumber(applyDefaultValue(frame.args.year,os.date("%Y")))
	
	local title = applyDefaultValue(frame.args.title,"")
	local col = applyDefaultValue(frame.args.col,4)
	local row = frame.args.row
	local float = "float" .. applyDefaultValue(frame.args.float,"left")
	
	if full_calendar then 
		
		addRow("{| class=\"" .. float .. "\" style=none\n")
		addRow("! colspan=" .. col .. " style=\"text-align:center; font-size:larger;\" | " .. title .. " " .. year .. "\n")
			
		for i=1, 12 do 
			if (i - 1) % col == 0 then addRow("|- style=\"vertical-align: top;\"\n") end
			addRow("|\n")
			_calendar(frame, i, true, "")
			addRow("\n")
		end
		
		addRow("|}")
	else 
		_calendar(frame, getMonthNumber(frame.args.month), false, float)	
	end

	--return "<nowiki>" .. table.concat(root) .. "</nowiki> \n" .. 
	return table.concat(root)
end

return p