模組:RailRouteTimer

local p = {
	DEFAULT_TRAIN_TIME_TEMPLATE = '往{station_link}方向:{train_time|H:i|次日}',
	DEFAULT_STATION_TRAIN_TIME_TEMPLATE = '{start_time|H:i|次日}-{end_time|H:i|次日}',
}

local function _convBool(val, default)
	if val == nil then
		return default
	elseif type(val) == 'boolean' then
		return val
	elseif mw.text.trim(val) == '' then
		return default
	end
	return (mw.text.trim(val) == '1')
end

local function literalize(str)
	return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(c) return "%" .. c end)
end

local function plainReplace(src, substr, repl)
	
	substr = literalize(substr)
	local rstr, _ = src:gsub(substr, repl)
	return rstr
end

local function _parseTime(time_str)
	local base_parts = mw.text.split(time_str, ':')
	local parts_size = table.getn(base_parts)
	local hour = 0
	local minute = 0
	local sec = 0
	if parts_size == 1 then
		minute = tonumber(mw.text.trim(base_parts[1]))
	elseif parts_size == 2 then
		hour = tonumber(mw.text.trim(base_parts[1]))
		minute = tonumber(mw.text.trim(base_parts[2]))
	elseif parts_size == 3 then
		hour = tonumber(mw.text.trim(base_parts[1]))
		minute = tonumber(mw.text.trim(base_parts[2]))
		sec = tonumber(mw.text.trim(base_parts[3]))
	else
		error('非法的时间输入')
	end
	return hour, minute, sec
end

local function _extractSeconds(totalsec)
	local hour = math.floor(totalsec / 3600)
	local minute = math.floor(totalsec % 3600 / 60)
	local sec = totalsec % 60
	return hour, minute, sec
end

local function _loadSystemData(system, raises)
	local system_data = nil
	local state
	if raises == nil then
		raises = true
	end
	if system ~= nil then
		state, system_data = pcall(mw.loadData, "Module:Adjacent stations/" .. system)
		if not state then
			if raises then
				error(string.format('铁道系统“%s”的数据模块不存在', system))
			else
				system_data = nil
			end
		end
	end
	return system_data
end

local function getLineData(system_data, line_code)
	if system_data.aliases then
		line_code = system_data.aliases[mw.ustring.lower(line_code)] or line_code
	end
	return system_data.lines[line_code]
end

function p._internalTrainTime(line_code, dir, type_, delta, system)
	local system_data = _loadSystemData(system)
	local dir_info = getLineData(system_data, line_code).routes[dir]
	local bhour, bmin, bsec = _parseTime(dir_info[type_])
	local dhour, dmin, dsec = _parseTime(delta)
	local totalsec = (bhour * 60 + bmin) * 60 + bsec + (dhour * 60 + dmin) * 60 + dsec
	local hour, minute, sec = _extractSeconds(totalsec)
	return dir_info["service end"], hour, minute, sec
end

function p._formatTemplateTime(frame, sparts, hour, minute, second)
	local tomorrow_prefix = ''
	if table.getn(sparts) >= 3 then
		if hour >= 24 then 
			tomorrow_prefix = mw.text.trim(sparts[3])
			if tomorrow_prefix == '' then tomorrow_prefix = '次日' end
			hour = hour - 24
		end
	end
	local time_str = string.format('%d:%d:%d', hour, minute, second)
	return tomorrow_prefix .. frame:callParserFunction{ name = '#time', args = { sparts[2], time_str } }
end

function p.trainTime(frame)
	local a = frame.args
	if a['type'] == 'f' or a['type'] == 'F' then a['type'] = 'first train' end
	if a['type'] == 'l' or a['type'] == 'L' then a['type'] = 'last train' end
	
	local terminal, hour, minute, sec = p._internalTrainTime(a.name, a.dir, a['type'], a.delta, a.system)
	return string.format('%02d:%02d', hour, minute)
end

function p.trainDirectionTime(frame)
	local str = frame.template
	local adj_result
	if str == nil then str = p.DEFAULT_TRAIN_TIME_TEMPLATE end
	
	local a = frame.args
	if a['type'] == 'f' or a['type'] == 'F' then a['type'] = 'first train' end
	if a['type'] == 'l' or a['type'] == 'L' then a['type'] = 'last train' end
	
	local terminal, hour, minute, sec = p._internalTrainTime(a.name, a.dir, a['type'], a.delta, a.system)
	
	local l = 0
	while true do
		local l, r, c = string.find(str, "%{([^%}]+)%}", l + 1)
		if l == nil then break end
		local sparts = mw.text.split(c, '|')
		if sparts[1] == 'station_name' then
			str = plainReplace(str, '{' .. c .. '}', terminal)
		elseif sparts[1] == 'station_link' then
			adj_result = require('Module:Adjacent stations')._station({
				system = a.system,
				station = terminal,
				line = nil,
				type = nil,
			}, frame)
			str = plainReplace(str, '{' .. c .. '}', adj_result)
		elseif sparts[1] == 'train_time' then
			str = plainReplace(str, '{' .. c .. '}', p._formatTemplateTime(frame, sparts, hour, minute, sec))
		end
	end
	return str
end

function p.stationTrainTime(frame)
	local a = frame.args
	local dir_reprs = {}
	local system_data = _loadSystemData(a.system)
	
	local min_time_data = 999999
	local max_time_data = -999999
	
	local line_names = mw.text.split(a.name, ',')
	
	for k, v in pairs(a) do
		if type(k) == 'number' then
			dir_reprs[k] = v
		end
	end
	
	for k, line_name in ipairs(line_names) do
		for i = 1, table.getn(dir_reprs), 2 do
			local dir = dir_reprs[i]
			local diff = dir_reprs[i + 1]
			local dir_times = getLineData(system_data, line_name).routes[dir]
	
			if dir_times ~= nil then
				local dh, dm, ds = _parseTime(diff)
				for _, dir_time in ipairs({dir_times["first train"], dir_times["last train"]}) do
					if dir_time ~= nil then
						local h, m, s = _parseTime(dir_time)
						local t = (h * 60 + m) * 60 + s + (dh * 60 + dm) * 60 + ds
						min_time_data = math.min(min_time_data, t)
						max_time_data = math.max(max_time_data, t)
					end
				end
			end
		end
	end
	
	local min_h, min_m, min_s = _extractSeconds(min_time_data)
	local max_h, max_m, max_s = _extractSeconds(max_time_data)
	
	local str = frame.template
	if str == nil then str = p.DEFAULT_STATION_TRAIN_TIME_TEMPLATE end
	local l = 0
	while true do
		local l, r, c = string.find(str, '%{([^%}]+)%}', l + 1)
		if l == nil then break end
		local sparts = mw.text.split(c, '|')
		if sparts[1] == 'start_time' then
			str = plainReplace(str, '{' .. c .. '}', p._formatTemplateTime(frame, sparts, min_h, min_m, min_s))
		elseif sparts[1] == 'end_time' then
			str = plainReplace(str, '{' .. c .. '}', p._formatTemplateTime(frame, sparts, max_h, max_m, max_s))
		end
	end
	return str
end

function p.testCases()
	local frame = mw.getCurrentFrame()
	
	local function mkfp(func_name)
		local fun = p[func_name]
		local function call_frame(frame)
			return fun(mw.getCurrentFrame():newChild(frame))
		end
		return call_frame
	end
	
	local fp = {
		trainDirectionTime = mkfp('trainDirectionTime'),
		stationTrainTime = mkfp('stationTrainTime'),
	}
	
	-- trainTime test
	if p.trainTime{args={name='1', dir='霞高', ['type']='L', delta='0:3', system='宁波轨道交通'}} ~= '22:03' then
		error('trainTime test failed: dir=霞高, type=L, delta=0:3.')
	end
	if p.trainTime{args={name='1', dir='高霞', ['type']='F', delta='0:4', system='宁波轨道交通'}} ~= '05:59' then
		error('trainTime test failed: dir=高霞, type=F, delta=0:4.')
	end
	
	-- trainDirectionTime test
	if fp.trainDirectionTime{args={name='1', dir='霞高', ['type']='L', delta='0:3', system='宁波轨道交通'}} ~= "往[[高桥西站 (宁波市)|高桥西]]方向:22:03" then
		error('trainDirectionTime test failed: dir=霞高, type=L, delta=0:3.')
	end
	if fp.trainDirectionTime{args={name='1', dir='高霞', ['type']='F', delta='0:4', system='宁波轨道交通'}} ~= "往[[霞浦站 (宁波市)|霞浦]]方向:05:59" then
		error('trainDirectionTime test failed: dir=高霞, type=F, delta=0:4.')
	end
	
	-- stationTrainTime test
	if fp.stationTrainTime{args={'高霞', '0:3', name='1', system='宁波轨道交通'}} ~= "05:58-22:36" then
		error('stationTrainTime test failed: 高霞, 0:3, name=1.')
	end
	if fp.stationTrainTime{args={'高霞', '0:3', '栎清', '0:1', name='1,2', system='宁波轨道交通'}} ~= "05:58-22:36" then
		error('stationTrainTime test failed: 高霞, 0:3, name=1,2.')
	end
end

return p