Module documentation
This documentation is transcluded from Template:No documentation/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:MusicTable/doc. [edit]
Module:MusicTable's function main is invoked by Template:MusicTable.
Module:MusicTable requires Module:Array.
Module:MusicTable requires Module:PageListTools.
Module:MusicTable requires Module:Paramtest.
Module:MusicTable requires Module:Yesno.

local p = {}

local contains = require('Module:Array').contains
local pagesWithCats = require('Module:PageListTools').pageswithcats
local paramTest = require('Module:Paramtest')
local yesNo = require('Module:Yesno')

local html = mw.html
local ask = mw.smw.ask
local log = mw.log

function buildRow(trackData, hints, members)
	local exclusivity = trackData.exclusive and '\'\'\'' or ''
	local event = trackData.event and '\'\''or ''
	local trackLink, _ = string.gsub(trackData.file, '%|[^%]]+%]', '|Play track]') -- Replace visual override on link
	local row = html.create('tr'):attr({ ['id'] = removeStringArticles(trackData.name), ['data-music-track-name'] = trackData.name })

	row:tag('td'):wikitext(event .. exclusivity .. '[[' .. trackData.name .. ']]' .. exclusivity .. event):done()
	if(hints) then
		row:tag('td'):wikitext(trackData.hint):done()
	end
	if(members == nil)then
		row:tag('td'):wikitext(yesNo(trackData.members) and '[[File:Member icon.png|link=|Members]]' or '[[File:Free-to-play icon.png|link=|Free-to-play]]'):done()
	end
	row:tag('td'):wikitext(trackData.duration):done()
		:tag('td'):wikitext(trackLink):done()
		
	return row
end

function createHeader(hints, members)
	local tabl = html.create('table'):addClass('wikitable sortable lighttable embed-audio-links qc-active music-tracks sticky-header')
	local row = tabl:tag('tr')
	row:tag('th'):wikitext('Name'):done()
	if(hints) then
		row:tag('th'):wikitext('Unlock hint'):done()
	end
	if(members == nil) then
		if(hints) then
			tabl:addClass(' align-center-3')
		else
			tabl:addClass(' align-center-2')
		end
		row:tag('th'):wikitext('Members'):done()
	end
	row:tag('th'):wikitext('Duration'):done()
		:tag('th'):addClass('unsortable'):wikitext('Music track'):done()
	return tabl
end

function addCategoryToData(data, cat, key)
	local pagesWithCat = pagesWithCats({ cat })
	
	for _, page in ipairs(data) do
		if(contains(pagesWithCat, page[1])) then
			page[key] = true
		else
			page[key] = false
		end
	end
	
	return data
end

-- Removes leading articles for sort function
-- Sorts alphabetically after stripping any `A `, `An `, or `The ` and appending it to the end
-- Also removies "illegal" characters
function removeStringArticles(str)
	if(string.find(str, '^A ')) then
		str = string.sub(str, 3)
		str = str .. ', A'
	elseif(string.find(str, '^The ')) then
		str = string.sub(str, 5)
		str = str .. ', The'
	elseif(string.find(str, '^An ')) then
		str = string.sub(str, 4)
		str = str .. ', An'
	end
	string.gsub(str, '!', '')
	string.gsub(str, '\'', '')
	return str
end

function tableConcat(mainTable, newTable)
    for i = 1, #newTable, 1 do
    	mainTable[#mainTable+1] = newTable[i]
    end
    return mainTable
end

function filterData(data)
	local pagesToExclude = pagesWithCats({ '[[Category:Unlisted music tracks]]' })
	
	local retData = {}
	for _, page in ipairs(data) do
		if(not contains(pagesToExclude, page[1])) then
			table.insert(retData, page)
		end
	end
	
	return retData
end


function loadData(limit, batchSize, hints, members)
	local allData = {}
	
	local query = {
		'[[Category:Music tracks]]' .. (members ~= nil and '[[Is members only::' .. members .. ']]' or ''),
		'?=#-',
		'?Music track title=name',
		(hints and '?Unlock hint=hint' or ''),
		(members == nil and '?Is members only=members' or ''),
		'?Music duration=duration',
		'?Music track=file',
	}
	query.limit = batchSize
	query.offset = 0
	
	for i = 0, limit, batchSize do
		query.limit = batchSize
		query.offset = i
		
		local t1 = os.clock()
		local smwData = ask(query)
		local t2 = os.clock()
		if(smwData == nil) then break end
		log(string.format('SMW: entries %d, time elapsed: %.3f ms.', #smwData, (t2 - t1) * 1000))
		smwData = filterData(smwData)
		tableConcat(allData, smwData)
	end
	
	assert(allData ~= nil and #allData > 0, 'SMW query failed')
	
	table.sort(allData, function(a, b) return removeStringArticles(a[1]) < removeStringArticles(b[1]) end)
	
	return allData
end

function p._main(args)
	local limit = paramTest.default_to(tonumber(args.limit), 5000) -- Acting as a real limiter
	local batchSize = paramTest.default_to(tonumber(args.batchsize), 250) -- 250 is slightly faster than 100 here

	local hints = yesNo(args.hints) or false
	local members = paramTest.default_to(args.members, nil)

	local data = loadData(limit, batchSize, hints, members)
	data = addCategoryToData(data, '[[Category:Old School-exclusive music]]', 'exclusive')
	data = addCategoryToData(data, '[[Category:Event music]]', 'event')
	
	local ret = createHeader(hints, members)
	for _, trackPage in ipairs(data) do
		ret:node(buildRow(trackPage, hints, members))
	end
	
	return ret
end

--[[DEBUG
p._main({})
p._main({['hints'] = 'Yes', ['members'] = 'No'})
]]

function p.main(frame)
	local args = frame:getParent().args
	return p._main(args)
end

return p